首先我們要知道艇搀,在Javascript中,所有的函數(shù)function和對(duì)象object都有一個(gè)屬性叫做原型求晶,不同的是函數(shù)的原型屬性寫成prototype焰雕,而對(duì)象的原型屬性在不同的瀏覽器里實(shí)現(xiàn)可能稍微有一點(diǎn)不一樣,不過一般都寫成__proto__芳杏。
我們來看下面的例子:
function Cat(name, color) {
? this.name = name;
? this.color = color;
}
Cat.prototype.age = 0;
var catC = new Cat("Fluffy", "White");
catC.__proto__.age = 4;
這里的Cat.prototype和catC.__proto__實(shí)際上指向了同一個(gè)對(duì)象矩屁,所以當(dāng)catC.__proto__.age的值被改為4時(shí)辟宗,Cat.prototype.age的值也變?yōu)榱?.
為了理解原型prototype的作用,我們先要了解Javascript中對(duì)象Object的構(gòu)造吝秕。簡單來說泊脐,Javascript的對(duì)象就是一堆屬性的集合,一個(gè)屬性由一個(gè)key(關(guān)鍵字或者屬性名)和value(值)組成烁峭。比如
{ name:"Fluffy", color: "White"}
這個(gè)對(duì)象由兩個(gè)屬性組成:name和color容客。
Javascript對(duì)象的屬性又分兩種,一種是內(nèi)部屬性约郁,就像上面這個(gè)對(duì)象中的name和color缩挑。這種屬性是只屬于這個(gè)對(duì)象的,對(duì)這樣的屬性值的訪問和修改只能通過對(duì)象的名字加屬性名來實(shí)現(xiàn)鬓梅,就像catC.name,catC.color供置。另外一種屬性叫外部屬性,就是通過原型prototype來實(shí)現(xiàn)的绽快。一個(gè)對(duì)象是否有對(duì)應(yīng)的內(nèi)部屬性可以通過hasOwnProperty方法來判斷芥丧,例如catC.hasOwnProperty('age')返回false,表示age不是catC的內(nèi)部屬性谎僻,雖然catC.age是可以可以訪問的值。
我們以訪問catC.age為例來說明外部屬性寓辱。當(dāng)Javascript引擎解析catC.age時(shí)艘绍,它首先會(huì)在catC的內(nèi)部屬性中尋找屬性age,這時(shí)發(fā)現(xiàn)catC這個(gè)對(duì)象沒有age的屬性秫筏,它會(huì)繼續(xù)在catC的__proto__這個(gè)屬性中找(從上面的討論中我們知道诱鞠,每一個(gè)對(duì)象都有一個(gè)原型屬性__proto__)。如何從__proto__中找呢这敬?我們知道__proto__是一個(gè)對(duì)象航夺,所以__proto__也有自己對(duì)象的內(nèi)部屬性,所以Javascript引擎會(huì)找__proto__是否有age這樣的內(nèi)部屬性崔涂,在上面的例子中我們發(fā)現(xiàn)有一個(gè)調(diào)用catC.__proto__.age = 4阳掐,這實(shí)際上是給catC.__proto__設(shè)置了內(nèi)部屬性age的值,這就是catC的外部屬性age冷蚂。如果在catC.__proto__中沒有找到相應(yīng)的屬性缭保,會(huì)怎么辦呢?我們知道catC.__proto__也是一個(gè)對(duì)象蝙茶,所以它也有__proto__屬性艺骂,所以Javascript引擎會(huì)繼續(xù)在catC.__proto__.__proto__中找,最終會(huì)找到系統(tǒng)的Object對(duì)象的prototype屬性隆夯。這個(gè)尋找的過程钳恕,像是循著一個(gè)鏈條找别伏,這個(gè)鏈條就叫原型鏈(Prototype Chain)。
我們?cè)賮矸治鲆幌聦?duì)象和函數(shù)中的原型(Prototype)的值是怎么設(shè)置的忧额。首先我們來看對(duì)象的原型是如何設(shè)置的厘肮。我們創(chuàng)建對(duì)象有幾種方式:
1.直接創(chuàng)建對(duì)象 { name:"Fluffy", color: "White"}:這時(shí)它的原型屬性__proto__的值是一個(gè)空的Object對(duì)象。
2.通過new方法使用函數(shù)創(chuàng)建 new Cat("Fluffy", "White"): 這時(shí)創(chuàng)建的對(duì)象的__proto__的值是函數(shù)的prototype屬性即Cat.prototype.
3.通過Object.create創(chuàng)建 Object.create(catC): 這時(shí)創(chuàng)建的對(duì)象的__proto__的值是catC.
我們?cè)賮砜春瘮?shù)的prototype屬性值宙址。對(duì)于一個(gè)函數(shù)而言轴脐,它的prototype屬性值缺省為一個(gè)沒有屬性的空對(duì)象。在Cat函數(shù)的例子中抡砂,Cat函數(shù)的prototype屬性最開始就是一個(gè)空的對(duì)象大咱,這個(gè)對(duì)象的對(duì)象名為函數(shù)名(Cat)。當(dāng)我們調(diào)用Cat.prototype.age = 0之后注益,Cat.prototype增加了一個(gè)age屬性碴巾。因?yàn)?b>Cat.prototype是一個(gè)對(duì)象,所以它也有一個(gè)原型屬性__proto__丑搔,這個(gè)原型屬性Cat.prototype.__proto__的值是一個(gè)空的Object對(duì)象厦瓢,空的Object對(duì)象也有__proto__屬性值,只不過屬性值為null啤月。
有了以上的知識(shí)煮仇,我們就能很容易判斷下面程序中的運(yùn)行結(jié)果(粗體的為運(yùn)行結(jié)果):
function Cat(name, color) {
? this.name = name;
? this.color = color;
}
Cat.prototype.age = 3;
var fluffy = new Cat("Fluffy", "White");
var scratchy = new Cat("Scratchy", "Black");
fluffy.age;
3
scratchy.age;
3
Cat.prototype.age = 4; //fluffy和scratchy的__proto__指向Cat.prototype,改變Cat.prototype.age的值谎仲,就相當(dāng)于改變fluffy和scratchy的age的值
fluffy.age;
4
scratchy.age;
4
Cat.prototype = {age: 5}; // fluffy和scratchy的__proto__指向的是原來Cat.prototype的那個(gè)對(duì)象浙垫,改變Cat.prototype所指向的對(duì)象并不會(huì)改變?cè)瓉韺?duì)象里的值
fluffy.age;
4
scratchy.age;
4
var muffin = new Cat("Muffin", "Brown"); //這個(gè)對(duì)象的__proto__指向的是當(dāng)前的Cat.prototype即{age:5}
muffin.age;
5
fluffy.age = 6; //這個(gè)相當(dāng)于添加了一個(gè)fluffy的內(nèi)部屬性,只會(huì)影響fluffy郑诺,不會(huì)影響其他對(duì)象
fluffy.age;
6
scratchy.age;
4
fluffy.__proto__.age = 7; //fluffy.__proto__和scratchy.__proto__指向的是同一個(gè)對(duì)象夹姥,改變一個(gè)會(huì)導(dǎo)致另外一個(gè)改變
fluffy.age;
7
scratchy.age;
7
從上面的討論中,我們可以看出原型prototype主要是用于為Javascript對(duì)象提供外部屬性辙诞。有了這一特性辙售,它可以在Javascript實(shí)現(xiàn)一些其他強(qiáng)大的功能,例如繼承飞涂。
簡單的旦部,我們通過函數(shù)創(chuàng)建出來的所有對(duì)象實(shí)例的原型__proto__都指向的是函數(shù)的原型,所以我們通過改變函數(shù)的原型就能改變所有對(duì)象實(shí)例的外部屬性较店,例如添加新的方法志鹃。在下面的例子中,可以給所有Cat的實(shí)例添加一個(gè)changeName的方法泽西。
function Cat(name, color) {
? this.name = name;
? this.color = color;
}
Cat.prototype.changeName = function(newName) {
? this.name = newName;
};
或者我們可以基于原有的函數(shù)曹铃,創(chuàng)建一個(gè)新的函數(shù),新函數(shù)可以繼承原來函數(shù)的所有方法和屬性捧杉。
function PersianCat(name, color, type) {
? Cat.call(this, name, color); // 調(diào)用Cat的構(gòu)造函數(shù)以設(shè)置內(nèi)部屬性
? this.type = type;
}
PersianCat.prototype = Object.create(Cat.prototype); // 這可以將Cat的原型做為上層原型陕见,繼承Cat的外部屬性秘血。
Object.defineProperty(PersianCat.prototype, 'constructor', {
? ? value: PersianCat,
? ? enumerable: false, // 這樣這個(gè)構(gòu)造函數(shù)就不會(huì)出現(xiàn)在'for in' 循環(huán)里
? ? writable: true }); // 通過這個(gè)來修改PersianCat的構(gòu)造函數(shù)的名字