前言
目前JavaScript的繼承方式有以下幾種:原型鏈繼承起愈,構(gòu)造函數(shù)繼承用狱,組合繼承,原型式繼承灵巧,寄生式繼承搀矫,寄生組合式繼承,ES6類繼承extends
1.原型鏈繼承
構(gòu)造函數(shù)刻肄、原型和實(shí)例之間的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象瓤球,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)原型對(duì)象的指針敏弃。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 這里是關(guān)鍵卦羡,創(chuàng)建SuperType的實(shí)例,并將該實(shí)例賦值給SubType.prototype
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); // true
優(yōu)點(diǎn):
- 父類方法可以復(fù)用
缺點(diǎn): - 父類的引用屬性會(huì)被所有子類實(shí)例共享麦到,多個(gè)實(shí)例對(duì)引用類型的操作會(huì)被篡改
- 子類構(gòu)建實(shí)例時(shí)不能向父類傳遞參數(shù)
2.構(gòu)造函數(shù)繼承
將父類構(gòu)造函數(shù)的內(nèi)容復(fù)制給了子類的構(gòu)造函數(shù)绿饵。這是所有繼承中唯一一個(gè)不涉及到prototype的繼承。核心代碼是SuperType.call(this)瓶颠,創(chuàng)建子類實(shí)例時(shí)調(diào)用SuperType構(gòu)造函數(shù)拟赊,于是SubType的每個(gè)實(shí)例都會(huì)將SuperType中的屬性復(fù)制一份。
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//繼承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
優(yōu)點(diǎn):
- 父類的引用屬性不會(huì)被共享
- 子類構(gòu)建實(shí)例時(shí)可以向父類傳遞參數(shù)
缺點(diǎn): - 只能繼承父類的實(shí)例屬性和方法步清,不能繼承原型屬性/方法
- 父類的方法不能復(fù)用要门,子類實(shí)例的方法每次都是單獨(dú)創(chuàng)建的
3.組合繼承
組合上述兩種方法就是組合繼承虏肾。用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,用借用構(gòu)造函數(shù)技術(shù)來實(shí)現(xiàn)實(shí)例屬性的繼承欢搜。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 繼承屬性
// 第二次調(diào)用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 繼承方法
// 構(gòu)建原型鏈
// 第一次調(diào)用SuperType()
SubType.prototype = new SuperType();
// 重寫SubType.prototype的constructor屬性封豪,指向自己的構(gòu)造函數(shù)SubType
SubType.prototype.constructor = SubType;
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
優(yōu)點(diǎn):
- 父類的方法可以被復(fù)用
- 父類的引用屬性不會(huì)被共享
- 子類構(gòu)建實(shí)例時(shí)可以向父類傳遞參數(shù)
缺點(diǎn): - 第一次調(diào)用SuperType():給SubType.prototype寫入兩個(gè)屬性name,color炒瘟。
- 第二次調(diào)用SuperType():給instance1寫入兩個(gè)屬性name吹埠,color。
- 實(shí)例對(duì)象instance1上的兩個(gè)屬性就屏蔽了其原型對(duì)象SubType.prototype的兩個(gè)同名屬性疮装。所以缘琅,組合模式的缺點(diǎn)就是在使用子類創(chuàng)建實(shí)例對(duì)象時(shí),其原型中會(huì)存在兩份相同的屬性/方法廓推。
4.原型式繼承
利用一個(gè)空對(duì)象作為中介刷袍,將某個(gè)對(duì)象直接賦值給空對(duì)象構(gòu)造函數(shù)的原型。原型式繼承的object方法本質(zhì)上是對(duì)參數(shù)對(duì)象的一個(gè)淺復(fù)制樊展。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
object()對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制呻纹,將構(gòu)造函數(shù)F的原型直接指向傳入的對(duì)象。另外专缠,ES5中存在Object.create()的方法雷酪,能夠代替上面的object方法。
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); //"Shelby,Court,Van,Rob,Barbie"
優(yōu)點(diǎn):
- 父類方法可以復(fù)用
缺點(diǎn): - 父類的引用屬性會(huì)被所有子類實(shí)例共享
- 子類構(gòu)建實(shí)例時(shí)不能向父類傳遞參數(shù)
5.寄生式繼承
核心:在原型式繼承的基礎(chǔ)上涝婉,增強(qiáng)對(duì)象哥力,返回構(gòu)造函數(shù)
function createAnother(original){
var clone = object(original); // 通過調(diào)用 object() 函數(shù)創(chuàng)建一個(gè)新對(duì)象
clone.sayHi = function(){ // 以某種方式來增強(qiáng)對(duì)象
alert("hi");
};
return clone; // 返回這個(gè)對(duì)象
}
函數(shù)的主要作用是為構(gòu)造函數(shù)新增屬性和方法,以增強(qiáng)函數(shù)
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺點(diǎn)(同原型式繼承):
- 原型鏈繼承多個(gè)實(shí)例的引用類型屬性指向相同墩弯,存在篡改的可能吩跋。
- 無法傳遞參數(shù)
6.寄生組合式繼承
結(jié)合借用構(gòu)造函數(shù)傳遞參數(shù)和寄生模式實(shí)現(xiàn)繼承,這是最成熟的方法渔工,也是現(xiàn)在庫實(shí)現(xiàn)的方法钞澳。
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 創(chuàng)建對(duì)象,創(chuàng)建父類原型的一個(gè)副本
prototype.constructor = subType; // 增強(qiáng)對(duì)象涨缚,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
subType.prototype = prototype; // 指定對(duì)象,將新創(chuàng)建的對(duì)象賦值給子類的原型
}
// 父類初始化實(shí)例屬性和原型屬性
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
// 借用構(gòu)造函數(shù)傳遞增強(qiáng)子類實(shí)例屬性(支持傳參和避免篡改)
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("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType 構(gòu)造函數(shù)策治,并且因此避免了在SubType.prototype 上創(chuàng)建不必要的脓魏、多余的屬性。于此同時(shí)通惫,原型鏈還能保持不變茂翔;因此,還能夠正常使用instanceof() 和isPrototypeOf()
7.ES6類繼承extends
extends關(guān)鍵字主要用于類聲明或者類表達(dá)式中履腋,以創(chuàng)建一個(gè)類珊燎,該類是另一個(gè)類的子類惭嚣。其中constructor表示構(gòu)造函數(shù),一個(gè)類中只能有一個(gè)構(gòu)造函數(shù)悔政,有多個(gè)會(huì)報(bào)出SyntaxError錯(cuò)誤,如果沒有顯式指定構(gòu)造方法晚吞,則會(huì)添加默認(rèn)的 constructor方法,使用例子如下:
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 輸出 200
-----------------------------------------------------------------
// 繼承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子類中存在構(gòu)造函數(shù)谋国,則需要在使用“this”之前首先調(diào)用 super()槽地。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 輸出 100
extends繼承的核心代碼如下,其實(shí)現(xiàn)和上述的寄生組合式繼承方式一樣
function _inherits(subType, superType) {
// 創(chuàng)建對(duì)象芦瘾,創(chuàng)建父類原型的一個(gè)副本
// 增強(qiáng)對(duì)象捌蚊,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
// 指定對(duì)象,將新創(chuàng)建的對(duì)象賦值給子類的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}