繼承
許多
OO語(yǔ)言都支持兩種繼承方式:接口繼承和實(shí)現(xiàn)繼承。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。由于函數(shù)沒有簽名,在ECMAScript中無(wú)法實(shí)現(xiàn)接口繼承,ECMAScript只支持實(shí)現(xiàn)繼承,而且其實(shí)實(shí)現(xiàn)繼承的主要是依靠原型鏈來實(shí)現(xiàn)的。
一、原型鏈
基本思想是利用原型讓一個(gè)引用型繼承另一個(gè)引用類型的屬性和方法。
構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例包含一個(gè)指向原型對(duì)象的內(nèi)部指針。
// 構(gòu)造函數(shù) SuperType
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
// 構(gòu)造函數(shù) SubType
function SubType() {
this.subproperty = false;
}
// 繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());
那么,讓原型對(duì)象等于另一個(gè)類型的實(shí)例,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型中也包含一個(gè)指向另一個(gè)原型的指針。假如另一個(gè)原型又是另一個(gè)原型的實(shí)例,那么上述關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。這就是所謂原型鏈的基本概念。
-
確定原型和實(shí)例的關(guān)系
a.
instanceofb.
isPrototypeOf() -
原型鏈的問題
在通過原型來實(shí)現(xiàn)繼承時(shí),原型實(shí)際上會(huì)變成另一個(gè)類型的實(shí)例。于是,原先的實(shí)例屬性也就順理成章地變成了現(xiàn)在的原型屬性。
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
3.1.SubType通過原型鏈繼承了SuperType之后,SubType.protype就變成了SubperType的一個(gè)實(shí)例,因此它也擁有了一個(gè)它自己的colors屬性,就跟專門創(chuàng)建了一個(gè)SubType.prototype.colors屬性一樣。結(jié)果SubType的所有實(shí)例都會(huì)共享這一個(gè)colors屬性。
3.2 創(chuàng)建子類型的實(shí)例時(shí),不能想超類型的構(gòu)造函數(shù)傳遞參數(shù)。不能在不影響所有對(duì)象類型的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)。
二、借用構(gòu)造函數(shù)
這種也稱為偽造對(duì)象或經(jīng)典繼承,即在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
2.1 傳遞參數(shù)
function SuperType(name) {
this.name = name;
}
function SubType() {
SuperType.call(this, "Nicholas");
this.age = 29;
}
var instance = new SubType();
alert(instance.name);
alert(instance.age);
2.2 構(gòu)造函數(shù)的問題
方法都在構(gòu)造函數(shù)中定義,無(wú)法做到函數(shù)復(fù)用
三、組合繼承
組合繼承,有時(shí)候也叫做偽經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。思路是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(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
組合繼承是最常用的繼承模式。
四、原型式繼承
借助已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。
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");
alert(person.friends);
ECMAScript5通過新增Object.create()方法規(guī)范化了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)用作新對(duì)象原型的對(duì)象和一個(gè)為新對(duì)象定義額外屬性的對(duì)象。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"],
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);
五、寄生式繼承
寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強(qiáng)對(duì)象,最后再像真地是它做了所有工作一樣返回對(duì)象。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = Object(original);
clone.sayHi = function () {
alert("Hi");
};
return clone;
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"],
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();
不能做到函數(shù)復(fù)用;
六、寄生組合繼承
組合繼承最大問題是無(wú)論什么情況下,都會(huì)調(diào)用兩次超類型構(gòu)造函數(shù):一次是創(chuàng)建子類型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
// 創(chuàng)建對(duì)象
var prototype = object(superType.prototype);
// 增強(qiáng)對(duì)象
prototype.constructor = subType;
// 指定對(duì)象
subType.prototype = prototype;
}
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);
};
只調(diào)用了一次SuperType構(gòu)造函數(shù),最理想的繼承方式