讀這篇之前,最好是已讀過(guò)我前面的關(guān)于對(duì)象的理解和封裝類(lèi)的筆記。
一、原型鏈
原型鏈最簡(jiǎn)單的理解就是:原型對(duì)象指向另一個(gè)構(gòu)造函數(shù)的實(shí)例。此時(shí)的原型對(duì)象包括一個(gè)指向另一個(gè)原型的指針,相應(yīng)的,另一個(gè)原型中的constructor指向另一個(gè)構(gòu)造函數(shù)。這種關(guān)系層層遞進(jìn),就通過(guò)一個(gè)原型對(duì)象鏈接另一個(gè)構(gòu)造函數(shù)的原型對(duì)象的方式實(shí)現(xiàn)了繼承。
下面用代碼和圖來(lái)詳細(xì)分析一下原型鏈中的各種關(guān)系:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
alert(instance.getSubValue()); //false
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
console.log(new SuperType());
console.log(instance);
下圖是上面代碼中打印出來(lái)的new SuperType()和instance的分析:

從上面的分析我們看到的原型鏈:
SubType的原型里有指向SuperType的原型的指針,SuperType的原型里有指向Object的原型的指針。
也可以看紅皮書(shū)里的圖:

- 訪(fǎng)問(wèn)屬性的搜索過(guò)程:
當(dāng)以讀取模式訪(fǎng)問(wèn)一個(gè)構(gòu)造函數(shù)(SubType)的實(shí)例的屬性時(shí),首先會(huì)在實(shí)例中搜索實(shí)例屬性。如果沒(méi)找到該屬性,則會(huì)繼續(xù)搜索實(shí)例的原型;SubType繼承了SuperType,那么實(shí)例的原型是另一個(gè)構(gòu)造函數(shù)(SuperType)的實(shí)例,搜索實(shí)例的原型也就是在SuperType的實(shí)例中搜索該屬性,沒(méi)找到繼續(xù)搜索SuperType的原型;SuperType繼承了Object,以此遞進(jìn),一層層搜索,直到找到或者搜到了原型鏈的末端停下來(lái)。 - 判斷原型和實(shí)例的關(guān)系
(1)instanceof
實(shí)例的原型鏈中出現(xiàn)過(guò)待檢測(cè)的構(gòu)造函數(shù),就會(huì)返回true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
(2)isPrototypeOf()方法
待檢測(cè)對(duì)象出現(xiàn)在instance的原型鏈中,就會(huì)返回true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
- 注意事項(xiàng)
(1)給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后。也就是
SubType.prototype = new SuperType();這句代碼一定要先寫(xiě),在寫(xiě)下面的代碼
//new method
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){
return false;
};
(2)在通過(guò)原型鏈實(shí)現(xiàn)繼承時(shí),不能使用對(duì)象字面量為原型添加屬性,因?yàn)檫@會(huì)重寫(xiě)原型鏈(具體請(qǐng)看理解對(duì)象篇里的一、創(chuàng)建對(duì)象)。
如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,會(huì)導(dǎo)致上一行代碼無(wú)效
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
其實(shí)這兩個(gè)注意事項(xiàng),只要你明白了(理解對(duì)象篇里的一、創(chuàng)建對(duì)象)后,根本不需要解釋。
- 原型鏈的問(wèn)題
(1)沒(méi)有辦法在不影響所有對(duì)象實(shí)例的情況下,給超類(lèi)型的構(gòu)造函數(shù)傳遞參數(shù)。
(2)在另一篇筆記封裝類(lèi)原型模式中提到過(guò),原型中的屬性是被共享的,但如果屬性的值時(shí)引用類(lèi)型,會(huì)有問(wèn)題的。而在繼承時(shí),原型實(shí)際上會(huì)是另一個(gè)類(lèi)型的實(shí)例(這個(gè)實(shí)例包含引用類(lèi)型值的實(shí)例屬性),那么原先的這個(gè)實(shí)例的實(shí)例屬性就會(huì)成為現(xiàn)在的原型屬性了,就會(huì)出現(xiàn)同樣的問(wèn)題了。共享了引用類(lèi)型值的屬性。
二、借用構(gòu)造函數(shù)
直接上代碼吧:
function SuperType(name){
this.name = name;
}
function SubType(){
//繼承了 SuperType ,同時(shí)還傳遞了參數(shù)
SuperType.call(this, "Nicholas");
//實(shí)例屬性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
如上寫(xiě)法就解決了原型鏈里的兩個(gè)問(wèn)題了,為什么呢?請(qǐng)看下面的講解:
SuperType,如果你用new調(diào)用它是構(gòu)造函數(shù),但你不用new,它就是個(gè)普通函數(shù)。SuperType.call(this, "Nicholas");不但傳遞了參數(shù),還綁定了子類(lèi)的作用域,就相當(dāng)于SuperType方法在幫助定義子類(lèi)的實(shí)例屬性。也就是說(shuō),即使SuperType的中定義的屬性里有引用類(lèi)型值,也不會(huì)成為子類(lèi)SubType的原型屬性,仍然時(shí)實(shí)例屬性。我們要時(shí)刻記住實(shí)例屬性是每個(gè)實(shí)例所私有的,而原型屬性是會(huì)被所有實(shí)例所共享的。
當(dāng)然這也寫(xiě)也不完美,問(wèn)題顯而易見(jiàn),和構(gòu)造函數(shù)模式同樣的問(wèn)題。
三、組合繼承
組合繼承,就像是封裝類(lèi)里的把構(gòu)造函數(shù)模式和原型模式組合使用是一樣的。這里是把原型鏈和借用構(gòu)造函數(shù)相組合。
簡(jiǎn)單來(lái)說(shuō)就是:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,通過(guò)借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承(父類(lèi)的實(shí)例屬性變成子類(lèi)的實(shí)例屬性)。
還是上代碼吧:
function SuperType(name){
this.name = name; this.colors = ["red", "blue", "green"];}
SuperType.prototype.sayName = function(){
alert(this.name);};function SubType(name, age){
SuperType.call(this, name); this.age = age;}
SubType.prototype = new SuperType();SubType.prototype.sayAge = function(){
alert(this.age);};var instance1 = new SubType("Nicholas", 29);instance1.colors.push("black");alert(instance1.colors); //"red,blue,green,black"instance1.sayName(); //"Nicholas";instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);alert(instance2.colors); //"red,blue,green"instance2.sayName(); //"Greg";instance2.sayAge(); //27
解釋:
下圖是instance1的打印

我們可以看到instance1具有了父類(lèi)SuperType的實(shí)例屬性name 、colors,但是子類(lèi)的原型是父類(lèi)的實(shí)例,所以原型中仍存在父類(lèi)的實(shí)例屬性,但是子類(lèi)已經(jīng)有了同樣的實(shí)例屬性name和colors,所以子類(lèi)原型中的這兩個(gè)屬性就被屏蔽了。從子類(lèi)訪(fǎng)問(wèn)它的name和colors屬性只會(huì)訪(fǎng)問(wèn)到它的實(shí)例屬性。
組合繼承是javaScript中最常用的繼承模式。而且instance和isPrototypeOf()也能夠用于識(shí)別給予組合繼承創(chuàng)建的對(duì)象類(lèi)型。
四、原型式繼承
感興趣可以了解一下。
原型鏈中,我們是讓原型對(duì)象指向一個(gè)構(gòu)造函數(shù)的實(shí)例,這個(gè)實(shí)例本質(zhì)上就是一個(gè)對(duì)象。原型式繼承就是讓原型對(duì)象指向一個(gè)已有的對(duì)象,不必創(chuàng)建自定義類(lèi)型。如下:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie”
大家還記得原型模式嗎。我的理解:這就是一個(gè)原型模式,區(qū)別是object這個(gè)方法就相當(dāng)于一個(gè)工廠,你傳給它一個(gè)對(duì)象,它就給你一個(gè)原型是這個(gè)對(duì)象的實(shí)例。這個(gè)實(shí)例就會(huì)相應(yīng)的繼承到了你傳給它的那個(gè)對(duì)象的屬性。
當(dāng)然你也可以不用自己寫(xiě)上面的object這個(gè)方法,因?yàn)镋S5提供了,而且更規(guī)范。ES5中新增了Object.create()方法規(guī)范化了原型式繼承。這個(gè)方法接受兩個(gè)參數(shù):一個(gè)是用做新對(duì)象原型的對(duì)象和(可選)一個(gè)為新對(duì)象定義額外屬性的對(duì)象(或者說(shuō)是定義新對(duì)象的實(shí)例屬性的對(duì)象,這個(gè)參數(shù)和defineProperties()方法的第二個(gè)參數(shù)格式相同:每個(gè)屬性都是通過(guò)自己的描述符定義的)。
上代碼:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
console.log(anotherPerson);
打印結(jié)果圖:
從上圖可以看到第二個(gè)參數(shù)定義的name屬性是新對(duì)象的實(shí)例屬性,它會(huì)屏蔽掉它的原型屬性里的同名屬性name。簡(jiǎn)單來(lái)說(shuō),Object.create就是用原型模式創(chuàng)建新對(duì)象的一個(gè)工廠,第一個(gè)參數(shù)定義了原型屬性,第二個(gè)參數(shù)定義了實(shí)例屬性。
五、寄生式繼承
這一小節(jié),感興趣了解一下。
六、寄生組合式繼承
前面說(shuō)過(guò),組合繼承是js里最常用的繼承模式,但是它并不完美。問(wèn)題是:調(diào)用了兩次超類(lèi)SuperType的構(gòu)造函數(shù),子類(lèi)創(chuàng)建了一部分多余的屬性(這部分屬性是超類(lèi)的實(shí)例屬性,在子類(lèi)的實(shí)例屬性里存在并有用,但在子類(lèi)的原型中也存在且沒(méi)用)。寄生組合式繼承就是解決這個(gè)問(wèn)題的。
上代碼:
function object(o){
function F(){}
F.prototype = o; return new F();}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object prototype.constructor = subType; //augment object subType.prototype = prototype; //assign object}
function SuperType(name){
this.name = name; this.colors = ["red", "blue", "green"];}
SuperType.prototype.sayName = function(){
alert(this.name);};function SubType(name, age){
SuperType.call(this, name); this.age = age;}
inheritPrototype(SubType, SuperType);SubType.prototype.sayAge = function(){
alert(this.age);};var instance1 = new SubType("Nicholas", 29);instance1.colors.push("black");alert(instance1.colors); //"red,blue,green,black"instance1.sayName(); //"Nicholas";instance1.sayAge(); //29var instance2 = new SubType("Greg", 27);alert(instance2.colors); //"red,blue,green"instance2.sayName(); //"Greg";instance2.sayAge(); //27console.log(instance1);console.log(SuperType.prototype)
代碼運(yùn)行打印結(jié)果圖:
從圖中可以看到instance1(子類(lèi)實(shí)例)的原型里已經(jīng)沒(méi)有了超類(lèi)的實(shí)例屬性name、colors。而且代碼中只運(yùn)行了一次超類(lèi)構(gòu)造函數(shù)。怎么做到的呢?請(qǐng)看下面的解釋?zhuān)?br> 我們先看這段代碼:
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
}
subType的原型還是指向了一個(gè)對(duì)象,這個(gè)對(duì)象是什么呢?object這個(gè)方法返回的對(duì)象,這個(gè)對(duì)象是一個(gè)構(gòu)造函數(shù)是空的,原型指向超類(lèi)原型的實(shí)例。什么意思呢?就是說(shuō)subType的原型還是一個(gè)構(gòu)造函數(shù)的實(shí)例,但不是超類(lèi)SuperType的實(shí)例,而是一個(gè)新建的臨時(shí)的空的構(gòu)造函數(shù)F的實(shí)例。看代碼:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
這個(gè)臨時(shí)的構(gòu)造函數(shù)F具有和超類(lèi)SuperType一樣的原型。那么這個(gè)時(shí)候的子類(lèi)的原型中就只有F的實(shí)例屬性和原型,而F的實(shí)例屬性是空的,就只有F的原型,F(xiàn)的原型就是超類(lèi)SuperType的原型。這樣子類(lèi)的實(shí)例屬性還是繼承了超類(lèi)的實(shí)例屬性,而子類(lèi)的原型屬性只繼承了超類(lèi)的原型。完美,就這樣。
啰嗦一句我對(duì)面向?qū)ο蟪绦蛟O(shè)計(jì)的理解,面向?qū)ο蟪绦蛟O(shè)計(jì)就是一直在說(shuō)如何使用對(duì)象。其實(shí),只要結(jié)果符合你的預(yù)期,對(duì)象真的是想怎么使用就怎么使用,不一定非得像書(shū)中說(shuō)的什么各種模式的。當(dāng)然書(shū)中的這么多種模式方法的介紹可以了解一下(但是構(gòu)造函數(shù)模式、原型模式。以及繼承里的原型鏈、借用構(gòu)造函數(shù)。還包括它們的組合使用還是需要認(rèn)真研讀,深刻理解的。再順便說(shuō)一句,繼承里的原型鏈、借用構(gòu)造函數(shù)可以看作是原型模式和構(gòu)造函數(shù)模式的進(jìn)化),可以加深自己對(duì)對(duì)象的理解,有助于你花式使用對(duì)象的方法。哈哈哈