我們知道javascript是基于原型(prototype)繼承的語言。關(guān)于javascript原型繼承的更多內(nèi)容請參閱之前的這篇文章淺談Javascript中的原型繼承,內(nèi)容絕對不會讓你失望。
不過本篇文章將會詳細闡述javascript中用于原型繼承的兩個重要的東西__proto__
和prototype
,以及由它們延伸出的一點容易讓人疑惑的方面。
__proto__
和prototype
的概念prototype
就是原型的意思,就是函數(shù)(構(gòu)造器)的一個屬性,每個函數(shù)都將會有一個prototype
屬性。此屬性是一個引用類型(指針),它指向函數(shù)的原型集對象。我們一般可以通過prototype
修改函數(shù)的原型屬性。而__proto__
是一個對象(可以是某個構(gòu)造器的實例)的屬性,它在對象(實例)被創(chuàng)建時跟隨被創(chuàng)建,它也是一個引用類型(指針),指向函數(shù)(構(gòu)造器)的prototype
屬性。
兩者的關(guān)系如下,
function Foo() {
}
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // true
額外提一點,__proto__
屬性目前在IE瀏覽器中貌似不能直接訪問,不過在Chrome和Firefox中是可以直接訪問的。
new
的過程如下代碼,
function Foo() {
}
var foo = new Foo();
我們使用new
創(chuàng)建了構(gòu)造器Foo
的一個實例foo
。那么這個過程究竟是怎么樣的呢?它將分為以下幾步,
foo
,并且foo = {}
foo
的__proto__
上,即foo.__proto__ = Foo.prototype
。這一步至關(guān)重要,為實例能夠訪問構(gòu)造器原型方法及原型鏈查找等操作做好了鋪墊。Foo
,并將對象foo
綁定到其上下文環(huán)境中,即Foo.call(foo)
。其實就是將Foo
中的this
指針指向foo
。下面讓我們來看個完整的例子來探索一下prototype
和__proto
屬性的相互關(guān)系,
function Animal() {
this.eats = true;
}
var cat = new Animal();
Animal.prototype.jumps = true;
console.log(cat.eats); // true;
console.log(cat.jumps); // true
// change constructor's prototype
Animal.prototype = {
bark: true
};
var dog = new Animal();
console.log(cat.bark); // undefined
console.log(dog.bark); // true
console.log(cat.__proto === Animal.prototype); // false;
console.log(dog.__proto === Animal.prototype); // true;
下面是一張關(guān)于上述代碼中Animal
(構(gòu)造器)及兩個實例(cat
及dog
)的原型關(guān)系圖,
從圖中我們可以看出,
cat
和dog
是Animal
構(gòu)造出的實例cat
的__proto__
屬性指向一個對象,此對象是Animal
改變prototype之前的指向。dog
的__proto__
屬性指向一個對象,此對象是Animal
改變prototype之后的指向。和Animal.prototype
的指向一致。cat
或者dog
的__proto__
屬性,經(jīng)過層層查找,最終的都會指向同一個對象,即Object.prototype
,而Object.prototype
的__proto__
指向null
,此時原型鏈已經(jīng)到頂。下面我們再來看個例子來探索一下javascript是如何在其原型鏈上進行屬性查找的,
function Person() {
this.name = 'gejiawen';
this.secret = function() {
console.log('I am a secret!');
};
};
Person.prototype.sayName = function() {
console.log('My name is ', this.name);
};
Person.prototype.secret = function() {
console.log('I am a secret on prototype!');
};
var p = new Person();
console.log(p.name); // gejiawen
console.log(p.secret()); // I am a secret!
console.log(p.sayName()); // My name is gejiawen
下面是實例對象p
在Chrome console中的打印,
所以,各個打印的解釋如下,
p.name
,直接在對象p
中找到了,將其打印出來。p.secret
,在對象p
中也直接找到了,執(zhí)行相應(yīng)方法。p.sayName
,在對象p
中并未找到相關(guān)定義,此時將會開始檢索p.__proto__
中對象,發(fā)現(xiàn)了sayName
的定義,此時就終止檢索,執(zhí)行相應(yīng)函數(shù)。有兩點需要額外提出來,
prototype
中定義了一個同名方法secret
,從上述代碼中可以看出,是不會執(zhí)行prototype
中的secret
函數(shù)。因為javascript在示例對象p
自身的定義找到secret
時就終止檢索了。p.__proto__
仍然未找到sayName
的相關(guān)定義,那么javascript會繼續(xù)向上檢索,繼續(xù)檢查p.__proto__.__proto__
,如此反復,直至到Object.prototype
。通過上面的示例,我們可以看出,示例對象的__proto__
在javascript的原型繼承模型中扮演著不可或缺的角色。
可以說__proto__
就是將一個個的對象串成一條完整原型鏈的粘合器。
個人覺得prototype
相對于__proto__
更加像是一個開放的接口,而__proto__
更傾向是原型鏈模型的內(nèi)部實現(xiàn)。我們在平時進行javascript開發(fā)時,可以在適當?shù)臅r候應(yīng)用一些prototype的知識來提高代碼質(zhì)量。
Object.create
方法Object.create
是ES5中引入的方法,
Object.create(proto[, propertiesObject])
MDN上給出的定義如下,
The
Object.create()
method creates a new object with the specified prototype object and properties.
意思就是我們可以在創(chuàng)建對象時,可以自定義其原型。(需要注意的是,當給Object.create
傳入的參數(shù)不是一個對象或者null
時,它將會拋出一個錯誤)
看下面的代碼,
var animal = {
eats: true
};
var rabbit = Object.create(animal);
console.log(rabbit.eats); // true
此時,rabbit.__proto__
上將會有eats
屬性,如關(guān)系如下圖,
請注意各個對象的__proto__
之間的關(guān)系。
instanceof
背后的邏輯我們知道instanceof
可以判斷某個實例對象是否是某個構(gòu)造器的實例。那么instanceof
判斷的依據(jù)是什么呢?
function A() {
}
var a = new A();
console.log(a instanceof A); // true
instanceof
的過程如下,
a.__proto__
和A.prototype
。true
,則表示a
即為A
的實例。false
,此時執(zhí)行類似這樣的操作a = a.__proto__
,然后重復第一步。其實說白了,instanceof
的實質(zhì)就是比較__proto__
和prototype
,我們來看個示例來看下是否能說明這個問題,
function Animal() {
this.name = 'wangcai';
}
function Dog() {
}
Dog.prototype = new Animal();
var dog = new Dog();
console.log(dog.name); // 'wangcai'
console.log(dog instanceof Animal); // true
// change the prototype of Class
Dog.prototype = {
name: 'yingcai'
};
console.log(dog instanceof Dog); // false
console.log(dog instanceof Animal); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
這個例子中,我們中間改變了Dog
的prototype
,此時造成的后果就是破壞了原先dog
對象的__proto__
與Dog.prototype
的引用關(guān)系,從而dog instanceof Dog
返回的結(jié)果為false
。
更多建議: