理解原型對象
我們創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)默認(rèn)的prototype
屬性屯伞,它指向一個(gè)對象豪直,對象中默認(rèn)的有一個(gè)叫做constructor
的屬性,指向這個(gè)函數(shù)本身末融。
如上圖暇韧,右側(cè)的這個(gè)方框就是函數(shù)function的原型,也就是說prototype屬性指向的這個(gè)對象就是原型巧婶。
當(dāng)構(gòu)造函數(shù)創(chuàng)建出一個(gè)新實(shí)例后,該實(shí)例會(huì)默認(rèn)具有一個(gè)__proto__
屬性英岭,這個(gè)屬性指向構(gòu)造函數(shù)的原型對象眼滤。因此,如果我們把屬性和方法都添加到原型對象中诅需,不同的實(shí)例就可以訪問到相同的屬性和方法了堰塌。
function Person() {
}
Person.prototype.name = "wanghan";
Person.prototype.age = 20;
Person.prototype.getName = function () {
console.log(this.name);
};
var fun1 = new Person();
var fun2 = new Person();
console.log(fun1.name); //wanghan
console.log(fun2.age); //20
fun1.getName(); //wanghan
fun2.getName(); //wanghan
console.log(fun1.getName == fun2.getName); //true
我們先聲明了一個(gè)空的構(gòu)造函數(shù)Person分衫,將一些屬性和方法添加到了Person函數(shù)的prototype屬性中,然后實(shí)例化了兩個(gè)對象牵现,根據(jù)后面的輸出我們可以確定兩個(gè)實(shí)例訪問的是相同的對象邀桑,它們共享這些屬性和方法。下圖展示了例子中各個(gè)對象的關(guān)系:
實(shí)例與原型中屬性的糾葛
當(dāng)為對象實(shí)例添加一個(gè)屬性時(shí)贼急,這個(gè)屬性就會(huì)屏蔽原型對象中保存的同名屬性捏萍。換句話說,添加這個(gè)屬性只會(huì)阻止我們?nèi)ピL問原型中的那個(gè)屬性走敌,但不會(huì)修改那個(gè)屬性逗噩。你也可以理解為,當(dāng)我們訪問實(shí)例對象時(shí)机打,會(huì)優(yōu)先訪問它自身的屬性和方法。
function Person() {
}
Person.prototype.name = "wanghan";
Person.prototype.age = 20;
Person.prototype.getName = function () {
console.log(this.name);
};
var fun1 = new Person();
var fun2 = new Person();
fun1.name = "Tom";
console.log(fun1.name); //Tom
console.log(fun2.name); //wanghan
fun1.getName(); //Tom
fun2.getName(); //wanghan
在這個(gè)例子中残邀,我們給實(shí)例fun1
添加了屬性name="Tom"
。
- 訪問
fun1.name
時(shí)驱闷,先在實(shí)例fun1
中尋找屬性name
空免,找到之后返回Tom
就不必再搜索原型了。 - 而訪問
fun2.name
時(shí)蹋砚,先在實(shí)例fun2
中沒有找到屬性name
,于是搜索原型循榆,結(jié)果找到了值為wanghan
的屬性name
,驗(yàn)證了在實(shí)例中添加屬性不會(huì)修改原型中的同名屬性秧饮。 - 訪問
fun1.getName()
時(shí)泽篮,在在實(shí)例fun1
中沒有找到方法getName()
,搜索原型找到了這個(gè)方法帽撑,執(zhí)行其中的代碼,此時(shí)方法中的this
已經(jīng)指向了實(shí)例(回憶一下操作符new調(diào)用構(gòu)造函數(shù)經(jīng)歷的過程)历恐,因此輸出的是實(shí)例屬性name
的值Tom
。
判斷屬性的位置
-
hasOwnProperty()
方法只在給定屬性存在于實(shí)例對象中時(shí)返回true
弱贼。 - 單獨(dú)使用
in
操作符時(shí)磷蛹,無論屬性存在于實(shí)例中還是原型中,只要能夠訪問到味咳,就會(huì)返回true
。
如果實(shí)例和原型中都存在著一個(gè)相同屬性责嚷,結(jié)合這兩種方法我們就可以判斷,我們訪問到的這個(gè)同名屬性到底是實(shí)例中的還是原型中的罕拂。
function Person() {
}
Person.prototype.name = "wanghan";
Person.prototype.age = 20;
var fun = new Person();
fun.name = "Tom";
console.log("name" in fun); //true
console.log(fun.hasOwnProperty("name")); //true --name來自實(shí)例
console.log("age" in fun); //true
console.log(fun.hasOwnProperty("age")); //false --age來自原型
更簡單的原型語法
在前面的例子中,我們給原型對象添加對象的時(shí)候衷掷,輸入了好多遍Person.prototype
柿菩,其實(shí)這些不必要的輸入都是可以避免的,最常見的方法就是以對象字面量的形式創(chuàng)建對象懦胞。
function Person() {
}
Person.prototype={
name: "wanghan",
age: 20,
getName: function () {
console.log(this.name)
}
};
var fun = new Person();
console.log(fun.name); //wanghan
console.log(fun.age); //20
fun.getName(); //wanghan
這樣創(chuàng)建對象是不是很輕松,而且輸出的結(jié)果跟之前相比并沒有什么變化医瘫。但是這里有一點(diǎn)需要注意旧困,重寫原型對象的實(shí)質(zhì)是吼具,我們重建了一個(gè)對象賦值給了函數(shù)的prototype
矩距,所以新建的這個(gè)原型對象中默認(rèn)的constructor
屬性也是新建的,它不指向構(gòu)造函數(shù)Person
陡蝇,但是必要情況下我們可以手動(dòng)讓它指向Person
哮肚。
function Person() {
}
Person.prototype={
constructor: Person, //看這里
name: "wanghan",
age: 20,
getName: function () {
console.log(this.name)
}
};
重寫原型對象的弊端
由于實(shí)例與原型之間的松散連接關(guān)系,即使我們先創(chuàng)建實(shí)例恼策,再給原型對象添加屬性潮剪,我們也照樣可以訪問到這些屬性。
function Person() {
}
var fun = new Person();
Person.prototype.name ="wanghan";
console.log(fun.name); //wanghan
但是重寫原型對象之后就不一樣了抗碰。
function Person() {
}
var fun = new Person();
Person.prototype={
constructor: Person,
name: "wanghan"
};
console.log(fun.name); //undefined
我們前面說過弧蝇,重寫原型對象實(shí)際上是新建了一個(gè)新的對象賦值給構(gòu)造函數(shù)的prototype
折砸。如果是先創(chuàng)建一個(gè)實(shí)例骤视,那么實(shí)例指向最開始的一個(gè)只含有constructor
屬性的原型對象,即使隨后又新建了一個(gè)原型對象睹逃,它的指向也不會(huì)再發(fā)生變化祷肯。
要想解決這個(gè)問題就要牢記佑笋,要在重寫原型對象之后新建實(shí)例,這樣實(shí)例指向的就是重寫之后的原型對象蒋纬。
function Person() {
}
Person.prototype={
constructor: Person,
name: "wanghan"
};
var fun = new Person();
console.log(fun.name); //wanghan
原型對象的問題
原型模式也不是沒有缺點(diǎn)。
- 原型中的所有屬性和方法都是被實(shí)例所共享的关摇,共享方法(函數(shù))是非常合適的碾阁,對于那些基本值的屬性也還說的過去,但是對于包含引用類型值得屬性來說宪睹,問題就非常突出了蚕钦。
function Person() {
}
Person.prototype={
constructor: Person,
name: "wanghan",
friends: ["zhangmin","yangfan"]
};
var fun1 = new Person();
var fun2 = new Person();
fun1.friends.push("Tom");
console.log(fun1.friends); //["zhangmin", "yangfan", "Tom"]
console.log(fun2.friends); //["zhangmin", "yangfan", "Tom"]
原型對象中有一個(gè)字符串?dāng)?shù)組,然后創(chuàng)建了兩個(gè)實(shí)例命贴,操作實(shí)例fun1
向原型對象的數(shù)組中又添加了一個(gè)字符串Tom
胸蛛,由于數(shù)組是引用類型值葬项,并且兩個(gè)實(shí)例共享原型對象中的屬性,所以我們剛剛的修改也會(huì)在實(shí)例fun2
中體現(xiàn)出來民珍。這個(gè)問題正是我們極少看到有人單獨(dú)使用原型模式的原因所在。
- 原型對象直接在原型對象里定義了屬性值嚷量,使所有實(shí)例默認(rèn)情況下都取得相同的屬性值,要克服這一缺點(diǎn)可以組合使用原型模式和構(gòu)造函數(shù)模式嗜历。
后記
我畫框圖是用電腦自帶的畫圖軟件畫的你敢信,舉得例子也是自己手打出來的梨州,運(yùn)行正確之后就剪切到markdown上面田轧。眼看著快要寫完了,時(shí)間也快凌晨兩點(diǎn)了傻粘,喜滋滋地準(zhǔn)備收尾,誰知道電腦突然一黑岛请,重啟了警绩。再次打開markdown之后我就絕望了肩祥,就剩下前兩段擺在上面。我冷靜了二十分鐘混狠,發(fā)了個(gè)朋友圈疾层,然后重寫到凌晨四點(diǎn)。嗨呀痛黎,好氣呀。