javaScript 建立物件的方式的四種模式探討,this指向問題,以及Jquery中物件的建立
在javaScript中,建立物件一共有四種方式,廢話少說:看程式碼
1 Json格式,字面量方式建立:
<script> var persion ={ name:"xiaoheng", age:20, getName:function(){ return this.name } } console.log(persion.getName())//可以取到資料 這種稱為JSON型別的資料格式,此時的this指的是window var aa = new persion(); console.log(aa.name)
//這樣就不可以 因為constructor構造器的並未對次物件引用,這個方法是靜態的。不能產生引用。
</script>
2 使用new建立物件
var dog = new Object();
dog.name = 'tim';
dog.getName = function() {
return dog.name;
}
- 可以使用
delete
刪除物件的屬性和方法
<script> var persion = new Object(); persion.name="xiaoheng"; persion.age=20; persion.number=30; //delete persion.name; function displayProp(obj){ //遍歷函式所有的屬性 var names="" for (var name in obj) { names+=name+":"+obj[name]+"," } alert(names) }; displayProp(persion); </script>
在window作用域中,不能使用delete刪除var, function定義的屬性和方法,可以刪除沒有使用var, function定義的屬性和方法
3 工廠模式建立物件。
function person(name) { var o = new Object(); o.name = name; o.getName = function() { return this.name; } return o; } var person1 = person('rose'); var person2 = person('jake');
這種方式類似與其他語言建立類的概念,
這種模式在函式的內部建立了一個空物件,然後逐一新增屬性和方法,最後返回,實現了物件得以複用的目的。但是存在2個很大的問題
- 無法識別物件的型別
console.log(person1 instanceof person); // false
- 每個物件呼叫的同名方法其實並不同一個方法
console.log(person1.getName == person2.getName); // false
其實就相當於每次宣告物件都被重新建立,只不過寫法上簡單了一點而已。
4. 自定義建構函式
var Person = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
var person1 = new Person('tom');
var person2 = new Person('tim');
注意 person1和person2能prototype擴充,他有構造器的作用
用著種方式建立並用prototype,可以實現偽繼承,(相當於對本物件做個擴充);
console.log(person1 instanceof Person); // ture
console.log(person1.getName == person2.getName); //false
從上面程式碼可以看出,物件的類別可以判斷了,person1就是Person的物件,可是2個同名方法任然不是同一個方法,而是重新建立,其實建構函式內部的實現,可以將上面的程式碼寫成這樣來理解
原型
原型並沒有那麼神祕,因為在javascript中,它無處不在。為了瞭解原型,我們可以在chrome瀏覽器的console中,隨意建立一個函式
function a(){}
然後繼續輸入
a.prototype
得到的結果如下
a {
constructor: function a(),
_proto_: Object
}
沒錯,得到的這個a,就是關鍵先生原型
了。每一個函式都有一個prototype
屬性,他就像一個指標一樣指向它的原型,而每一個原型,都有一個constructor
屬性,指向他的建構函式。
那麼原型在建立物件中有什麼用呢?
一個例子,千錘百煉,如下
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
var person1 = new Person('rose');
var person2 = new Person('Jake');
當然也可以將屬性寫入原型中,但是如果那樣的話,屬性就會如同方法一樣被公用了,因此一般來說,屬性會寫入建構函式之中,方法寫入原型之中。當然,這視情況而定。
如果你想進一步瞭解原型,可以看下圖。
當我們使用new Person
時便會建立一個例項,比如這裡的person1與person2,這裡的例項中,會有一個_proto_
屬性指向原型。
- 原型中的查詢機制
當我們使用例項person1
呼叫方法person.getName()
時,我們首先找的,是看看建構函式裡面有沒有這個方法,如果建構函式中存在,就直接呼叫建構函式的方法,如果建構函式不存在,才回去查詢原型中是否存在該方法
於是,這裡便會有一個十分重要的概念需要理解,那就是this的指向問題。
在整個建立物件的過程當中,this到底指向誰?
function Person(name) {
this.name = name;
// this.getName = function() {
// return 'constructor';
// }
}
Person.prototype.getName = function() {
return this.name;
}
Person.prototype.showName = function() {
return this.getName();
}
var rose = new Person('rose');
console.log(rose.showName()); //rose
其實在new
執行時,建構函式中的this與原型中的this都被強行指向了new
建立的例項物件。
如果需要寫在原型上的方法很多的話,還可以這樣來寫,讓寫法看上去更加簡潔
Person.prototype = {
constructor: Person,
getName: function(){},
showName: function(){},
...
}
constructor:Person
這一句必不可少,因為{}
也是一個物件,當使用Person.prototype = {}
時,相當於重新定義了prototype的指向,因此手動修正{}
的constructor
屬性,讓他成為Person的原型。
5. jQuery中建立物件是如何實現的?
其實通過上面方式,使用建構函式宣告例項的專屬變數和方法,使用原型宣告公用的例項和方法,已經是建立物件的完美解決方案了。可是唯一的不足在於,每次建立例項都要使用new來宣告。這樣未免太過麻煩,如果jquery物件也這樣建立,那麼你就會看到一段程式碼中有無數個new,可是jQuery僅僅只是使用了$('xxxx')便完成了例項的建立,這是如何做到的呢?
還是按照慣例,先貼程式碼。
(function(window, undefined) {
var Person = function(name) {
return new Person.fn.init(name);
}
Person.prototype = Person.fn = {
constructor: Person,
init: function(name) {
this.name = name;
return this;
},
getName: function() {
return this.name;
}
}
Person.fn.init.prototype = Person.fn;
window.Person = window.$ = Person;
})(window);
console.log($('tom').getName());
一步一步來分析
- 首先為了避免變數汙染,使用了函式自執行的方式。這種方式讓javascript程式碼具備了模組的特性,因此大多數js庫都會這樣做
(function(){
...
})()
傳入window引數,是為了讓jquery物件在外window中可以被訪問,因此有如下一句程式碼
window.Person = window.$ = Person;
這樣我們就可以直接使用$
來呼叫Person建構函式
- 關鍵問題在於,真正的建構函式並不是Person,而是Person原型中的
init
方法。其中的複雜關係,我們藉助下圖來分析瞭解,表達能力實在有限,也不知道如何才能表達的更加簡潔易懂。
當外部呼叫$().getName()
時,函式內部的執行順序如下
new Person.fn.init()
// 而init的原型,通過下面一句指向了Person的原型
Person.fn.init.prototype = Person.fn;
// 於是就可以呼叫原型中的getName方法了