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