點(diǎn)擊此處訪問(wèn)我的github了解更多詳情
在基于類的語(yǔ)言中,對(duì)象是類的實(shí)例,并且類可以從另一個(gè)類繼承,如Java;JavaScript則是一門(mén)基于原型的語(yǔ)言,以原型鏈實(shí)現(xiàn)繼承,其對(duì)象可以直接繼承自另一對(duì)象,此篇詳細(xì)闡述JavaScript之原型與原型鏈。
原型
Javascript中創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的作用即是包含可以由特定類型實(shí)例共享的屬性和方法,這個(gè)對(duì)象就是函數(shù)的原型對(duì)象。
默認(rèn)情況,所有的原型對(duì)象都會(huì)有一個(gè)constructor屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。
調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,實(shí)例的內(nèi)部將包含一個(gè)指向構(gòu)造函數(shù)原型對(duì)象的指針,在ECMA-262中定義此指針為[[Prototype]],并不能被顯式的訪問(wèn)到,而在Firefox,Safari和Chrome中每個(gè)對(duì)象上有一個(gè)__proto__屬性。
__proto__顯示的是實(shí)例與構(gòu)造函數(shù)原型對(duì)象間的關(guān)系,而非實(shí)例與構(gòu)造函數(shù)間的關(guān)系。
function Animal(name) {
this.name = name;
}
Animal.prototype.age = 3;
Animal.prototype.getName = function() {
return this.name;
};
var animal = new Animal('Dog');
console.log(animal.getName()); //輸出Dog
以上代碼中,Animal為構(gòu)造函數(shù),Animal.prototype指向構(gòu)造函數(shù)原型對(duì)象;原型對(duì)象中constructor屬性指向構(gòu)造函數(shù),即Animal.prototype.constructor指向Animal;在構(gòu)造函數(shù)實(shí)例中,其__proto__屬性指向構(gòu)造函數(shù)原型對(duì)象。
- 原型與實(shí)例屬性訪問(wèn)
function Animal(name) {
this.name = name;
}
Animal.prototype.age = 3;
Animal.prototype.getName = function() {
return this.name;
};
var animal1 = new Animal('Dog');
var animal2 = new Animal('Cat');
console.log(animal1.age); //輸出3--原型屬性
console.log(animal2.age); //輸出3--原型屬性
animal2.age = 4;
console.log(animal2.age); //輸出4--實(shí)例屬性
delete animal2.age;
console.log(animal2.age); //輸出3--原型屬性
獲取某對(duì)象屬性時(shí),首先從該對(duì)象實(shí)例本身開(kāi)始,若該實(shí)例中找到該屬性,則返回該屬性值;若未找到,則繼續(xù)查找該實(shí)例對(duì)象指向的構(gòu)造函數(shù)原型對(duì)象,若找到則返回值。
(1) hasOwnProperty()方法
hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是在原型上還是實(shí)例上,只有當(dāng)給定屬性為對(duì)象實(shí)例屬性時(shí)返回true:
function Animal(name) {
this.name = name;
}
Animal.prototype.age = 3;
Animal.prototype.getName = function() {
return this.name;
};
var animal1 = new Animal('Dog');
var animal2 = new Animal('Cat');
console.log(animal1.hasOwnProperty('name')); //true
console.log(animal1.hasOwnProperty('age')); //false
animal1.age = 4;
console.log(animal1.hasOwnProperty('age')); //true
(2) 原型與in
單獨(dú)使用in操作符時(shí),只要通過(guò)對(duì)象能訪問(wèn)到給定屬性即返回true,無(wú)論屬性是在實(shí)例還是原型上定義:
function Animal(name) {
this.name = name;
}
Animal.prototype.age = 3;
Animal.prototype.getName = function() {
return this.name;
};
var animal1 = new Animal('Dog');
var animal2 = new Animal('Cat');
console.log(animal1.hasOwnProperty('name')); //true
console.log('name' in animal1); //true
console.log(animal1.hasOwnProperty('age')); //false
console.log('age' in animal1); //true
console.log('eat' in animal1); //false
- 2.實(shí)例與原型的引用關(guān)系
實(shí)例在創(chuàng)建時(shí),其內(nèi)部指針[[Prototype]](在上文提到的__proto__)指向構(gòu)造函數(shù)原型對(duì)象,存在引用關(guān)系。
function Animal(name) {
this.name = name;
}
var animal = new Animal('Dog');
Animal.prototype.age = 3;
Animal.prototype.getName = function() {
return this.name;
};
animal.getName(); //輸出Dog
可以看到雖然實(shí)例早于原型中g(shù)etName方法創(chuàng)建,但其依然可以調(diào)用該方法,因?yàn)閷?shí)例通過(guò)引用指向原型對(duì)象,原型對(duì)象變化自然能被實(shí)例訪問(wèn)到。然而,對(duì)于如下這種情況:
function Animal(name) {
this.name = name;
}
var animal = new Animal('Dog');
Animal.prototype = {
constructor: Animal, //設(shè)置constructor值為Animal,確保constructor屬性返回適當(dāng)值,詳細(xì)見(jiàn)上文關(guān)于constructor屬性說(shuō)明
age: 3,
getName: function() {
return this.name;
}
};
var animal2 = new Animal('Cat');
console.log(animal2.getName()); //輸出Cat
animal.getName(); //TypeError: undefined is not a function
此處,首先創(chuàng)建了一個(gè)實(shí)例,隨后重寫(xiě)了構(gòu)造函數(shù)原型對(duì)象,再在實(shí)例上調(diào)用getName方法時(shí)報(bào)錯(cuò);而重寫(xiě)構(gòu)造函數(shù)原型對(duì)象之后創(chuàng)建的實(shí)例調(diào)用getName方法可以正常返回對(duì)應(yīng)值。這是因?yàn)橹貙?xiě)原型對(duì)象之后,之前創(chuàng)建的實(shí)例引用的依然是之前的原型對(duì)象,其與現(xiàn)有原型之間并無(wú)聯(lián)系,而之后創(chuàng)建的實(shí)例[[Prototype]]指針引用的就是現(xiàn)有原型。
- 3.原型對(duì)象存在問(wèn)題
function Animal(name) {
this.name = name;
}
Animal.prototype.age = 3;
Animal.prototype.partner = ['one'];
Animal.prototype.getName = function() {
return this.name;
};
var animal1 = new Animal('Dog');
var animal2 = new Animal('Cat');
console.log(animal1.partner); //輸出["one"]
console.log(animal2.partner); //輸出["one"]
animal1.partner.push('two');
console.log(animal1.partner); //輸出["one", "two"]
console.log(animal2.partner); //輸出["one", "two"]
console.log(animal1.partner === animal2.partner); //輸出true
可以看到原型中所有屬性都被實(shí)例共享,特別是對(duì)于引用類型的屬性,如上的partner屬性。
- 4.默認(rèn)原型
所有引用類型默認(rèn)都繼承自O(shè)bject,所有構(gòu)造函數(shù)默認(rèn)原型都是Object的實(shí)例,默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向Object.prototype。
- 5.構(gòu)造函數(shù),原型與實(shí)例的關(guān)系
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,由prototype屬性指向;原型對(duì)象包含一個(gè)指向構(gòu)造函數(shù)的指針constructor;而實(shí)例都包含一個(gè)指向構(gòu)造函數(shù)原型對(duì)象的內(nèi)部指針[[Prototype]]。
原型鏈
每一個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,當(dāng)我們讓某一原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,此時(shí)該原型對(duì)象就包含一個(gè)指針,該指針指向這一構(gòu)造函數(shù)的原型對(duì)象,該指針指向的原型對(duì)象中包含一個(gè)指向這一構(gòu)造函數(shù)的指針,同樣我們可以令該指針指向的原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,如此遞進(jìn),則形成一條實(shí)例與原型的鏈條,即原型鏈。
function Parent() {
this.name = 'parent';
};
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
this.childname = 'child';
}
Child.prototype = new Parent();
Child.prototype.getChildName = function() {
return this.childname;
};
var child = new Child();
console.log(child.getName()); //輸出parent
console.log(child.getChildName()); //輸出child
如上代碼,child實(shí)例指向Child原型,Child原型等于Parent實(shí)例,即指向Parent原型。可見(jiàn)本質(zhì)即是以一個(gè)新類型(構(gòu)造函數(shù))的實(shí)例重寫(xiě)原型對(duì)象,形成原型鏈。
- 原型鏈問(wèn)題
既然原型對(duì)象存在問(wèn)題,那么原型鏈自然也繼承了這個(gè)問(wèn)題,即原型屬性會(huì)被所有實(shí)例共享,對(duì)于原型屬性的改變將影響所有實(shí)例,而在原型鏈中,由于某一原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,實(shí)例受影響,也就導(dǎo)致其他原型對(duì)象也受影響。
原型與原型鏈?zhǔn)荍avaScript實(shí)現(xiàn)繼承的基礎(chǔ),下一篇詳細(xì)介紹JavaScript之繼承。