ES5 繼承
作者:assassin cike
來源:SegmentFault 思否社區(qū)
首先要明白兩點:
一、非方法屬性每個子類實例需要獨立
二、方法屬性每個子類實例需要共享
為什么?
如果非方法屬性為引用類型,且非方法屬性共享,在一個實例中改變,其他實例中就會做出改變,這樣每個實例就會相互影響,而方法屬性一般是不需要進(jìn)行改變的,只是對方法調(diào)用。
方法跟屬性分別可以定義在構(gòu)造函數(shù)內(nèi)部跟prototype上。
繼承的目的是子類繼承父類的方法跟屬性。
代碼主要來自于紅寶書4
基于原型鏈的繼承
每個函數(shù)都有個prototype屬性,每個對象都有__proto__屬性(在chrome中表現(xiàn)如此,prototype也是如此) 如圖,屬性的查找會從當(dāng)前層級依次向原型鏈上查找,直到查找到原型鏈的頂端null,具體可參考:
js?proto(https://www.jianshu.com/p/cd26f07df9ba)

既然屬性的查找是按照原型鏈向上查找的,且繼承就是繼承父類的屬性跟方法,那么就可以利用這個特性,進(jìn)行繼承。
function?SuperType()?{
??this.property?=?true;
}
SuperType.prototype.getSuperValue?=?function?()?{
??return?this.property;
};
function?SubType()?{
??this.subproperty?=?false;
}
//?繼承SuperType
SubType.prototype?=?new?SuperType();
SubType.prototype.getSubValue?=?function?()?{
??return?this.subproperty;
};
let?instance?=?new?SubType();
console.log(instance.getSuperValue());?//?true?可以正確調(diào)用父類的方法,拿到父類的屬性
原型雖然實現(xiàn)了繼承,但是還是有缺點的
劣勢:
1. 子類或者父類的屬性為引用類型時,改變一個實例的引用類型屬性,其他實例的該引用類型屬性也會發(fā)生改變,這樣其實例就會相互污染了。
function?SuperType()?{
??this.colors?=?["red",?"blue",?"green"];
}
function?SubType()?{}?//?繼承SuperType
SubType.prototype?=?new?SuperType();
let?instance1?=?new?SubType();
instance1.colors.push("black");
console.log(instance1.colors);
//?"red,blue,green,black";
let?instance2?=?new?SubType();
console.log(instance2.colors);
//?"red,blue,green,black";
為什么非方法屬性不寫在prototype上?
因為prototype上的屬性的共享的,在一個實例上改了該屬性,其他實例的該屬性也會被改掉。
為什么方法不寫在構(gòu)造函數(shù)內(nèi)部?
方法寫在子類內(nèi)部:每次實例化構(gòu)造函數(shù),方法都是新的;方法只是用來調(diào)用,不需要修改,所以實例共享就行了。
方法寫在父類內(nèi)部:不同的子類繼承父類都需要實例化父類;方法只是用來調(diào)用,不需要做修改,所以實例共享就行了,包括子類實例。如果子類需要修改父類方法,直接在子類中定義相同方法名,進(jìn)行覆蓋就行了。
2. 子類在實例化時不能給父類的構(gòu)造函數(shù)傳參,因為父類的實例化是在前面,而不是構(gòu)造函數(shù)調(diào)用的時候。
盜用構(gòu)造函數(shù)
為了解決父類中屬性為引用類型導(dǎo)致子類實例化后,引用屬性共享的問題,跟父類構(gòu)造函數(shù)無法傳參的問題。引入了“盜用構(gòu)造函數(shù)“方式實現(xiàn)繼承。思路是在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)。
不同實例的引用屬性不會相互影響
function?SuperType()?{
??this.colors?=?["red",?"blue",?"green"];
}
function?SubType()?{
??//?繼承SuperType
??SuperType.call(this);
}
let?instance1?=?new?SubType();
instance1.colors.push("black");
console.log(instance1.colors);
//?"red,blue,green,black";
let?instance2?=?new?SubType();
console.log(instance2.colors);
//?"red,blue,green";
可以為父類構(gòu)造函數(shù)傳參
function?SuperType(name)?{
??this.name?=?name;
}
function?SubType(name)?{
??//?繼承SuperType并傳參
??SuperType.call(this,?name);
??//?實例屬性
??this.age?=?29;
}
let?instance?=?new?SubType("geek");
console.log(instance.name);?//?"geek";
console.log(instance.age);?//?29
定義在父類prototype上的方法,子類無法繼承
function?SuperType(name)?{
??this.name?=?name;
}
SuperType.prototype.say?=?function?()?{
??console.info("hello");
};
function?SubType(name)?{
??//?繼承SuperType并傳參
??SuperType.call(this,?name);
??//?實例屬性
??this.age?=?29;
}
let?instance?=?new?SubType("geek");
console.log(instance.name);?//?"geek";
console.log(instance.age);?//?29
instance.say()?//?獲取不到該函數(shù)
定義在父類構(gòu)造函數(shù)中方法無法共享
組合繼承
function?SuperType(name)?{
??this.name?=?name;
??this.colors?=?["red",?"blue",?"green"];
}
SuperType.prototype.sayName?=?function?()?{
??console.log(this.name);
};
function?SubType(name,?age)?{
??//?繼承屬性,綁定上下文為SubType的實例
??SuperType.call(this,?name);
??this.age?=?age;
}
//?繼承方法
SubType.prototype?=?new?SuperType();
SubType.prototype.sayAge?=?function?()?{
??console.log(this.age);
};
let?instance1?=?new?SubType("Nicholas",?29);
instance1.colors.push("black");
console.log(instance1.colors);
//?"red,blue,green,black"
instance1.sayName();?//?"Nicholas";
instance1.sayAge();?//?29
let?instance2?=?new?SubType("Greg",?27);
console.log(instance2.colors);
//?"red,blue,green";
instance2.sayName();?//?"Greg";
instance2.sayAge();?//?27
可以傳遞參數(shù)到父類構(gòu)造函數(shù)
兩個實例中的引用類型不會相互影響
實例可以調(diào)用父類的方法,且實現(xiàn)方法的共享
組合繼承也保留了 instanceof 操作符和isPrototypeOf() 方法識別合成對象的能力。
SuperType會被調(diào)用兩次,SubType實例跟原型鏈上都有name跟colors屬性。
原型式繼承
function?object(o)?{
??function?F()?{}
??F.prototype?=?o;
??return?new?F();
}
let?person?=?{
??name:?"Nicholas",
??friends:?["Shelby",?"Court",?"Van"],
};
let?anotherPerson?=?object(person);
anotherPerson.name?=?"Greg";
anotherPerson.friends.push("Rob");
let?yetAnotherPerson?=?object(person);
yetAnotherPerson.name?=?"Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);
//?"Shelby,Court,Van,Rob,Barbie";
let?person?=?{
??name:?"Nicholas",
??friends:?["Shelby",?"Court",?"Van"],
};
let?anotherPerson?=?Object.create(person);
anotherPerson.name?=?"Greg";
anotherPerson.friends.push("Rob");
let?yetAnotherPerson?=?Object.create(person);
yetAnotherPerson.name?=?"Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);
//?"Shelby,Court,Van,Rob,Barbi
父對象的引用類型會在實例中共享,這樣就會相互污染。
寄生式繼承
工廠函數(shù)的定義:
function?createAnother(original)?{
??let?clone?=?object(original);
??//?通過調(diào)用函數(shù)創(chuàng)建一個新對象;
??clone.sayHi?=?function?()?{
????//?以某種方式增強這個對象;
????console.log("hi");
??};
??return?clone;?//?返回這個對象
}
let?person?=?{
??name:?"Nicholas",
??friends:?["Shelby",?"Court",?"Van"],
};
let?anotherPerson?=?createAnother(person);
anotherPerson.sayHi();?//?"hi"
父對象的引用類型會在實例中共享,這樣就會相互污染。
方法無法實現(xiàn)共享
寄生式組合繼承
先看下組合繼承:
function?SuperType(name)?{
??this.name?=?name;
??this.colors?=?["red",?"blue",?"green"];
}
SuperType.prototype.sayName?=?function?()?{
??console.log(this.name);
};
function?SubType(name,?age)?{
??SuperType.call(this,?name);?//?第二次調(diào)用,將父類的屬性綁定到子類的實例中
??SuperType();
??this.age?=?age;
}
SubType.prototype?=?new?SuperType();
//?第一次調(diào)用SuperType();
SubType.prototype.constructor?=?SubType;
SubType.prototype.sayAge?=?function?()?{
??console.log(this.age);
};
父類的屬性是需要的,父類的原型上的方法是需要的,重復(fù)的父類屬性不需要,由上圖可見重復(fù)的父類屬性是由于實例化父類給子類原型造成的,我們不去實例化父類,而是將父類的原型傳遞給子類的原型就行了,結(jié)合原型式繼承特點可以做到
function?SuperType(name)?{
??this.name?=?name;
??this.colors?=?["red",?"blue",?"green"];
}
SuperType.prototype.sayName?=?function?()?{
??console.log(this.name);
};
function?SubType(name,?age)?{
??SuperType.call(this,?name);?//?將父類的屬性綁定到SubType實例中
??this.age?=?age;
}
SubType.prototype?=?Object.create(SuperType.prototype);
//?將子類的prototype關(guān)聯(lián)到父類的prototype上
SubType.prototype.sayAge?=?function?()?{
??console.log(this.age);
};

SubType.prototype?=?Object.create(SuperType.prototype,?{
??constructor:?{
????value:?SubType,?//?修正?constructor?指向
????writable:?true,
????configurable:?true,
??},
});

SuperType的constructor出現(xiàn)了,其實constructor并沒什么用,只是個約定罷了,參考賀老的解釋JavaScript 中對象的 constructor 屬性的作用是什么?(https://www.zhihu.com/question/19951896/answer/13457869)
算是引用類型繼承的最佳模式


