點(diǎn)擊此處訪(fǎng)問(wèn)我的github了解更多詳情
在基于類(lèi)的語(yǔ)言中,對(duì)象是類(lèi)的實(shí)例砌滞,并且類(lèi)可以從另一個(gè)類(lèi)繼承侮邀,如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ì)象的作用即是包含可以由特定類(lèi)型實(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]],并不能被顯式的訪(fǎng)問(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í)例屬性訪(fǎng)問(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ì)象能訪(fǎng)問(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í)例訪(fǎng)問(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ì)于引用類(lèi)型的屬性岖寞,如上的partner屬性。
- 4.默認(rèn)原型
所有引用類(lèi)型默認(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è)新類(lèi)型(構(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之繼承葬凳。