這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
1. 原型鏈
溫故而知新:
建構式、原型和實體的關系:
??每個建構式都有一個原型物件,原型有一個屬性指回建構式,實體有一個內部指標指向原型,
思考:如果原型是另一個型別的實體呢?
??那就意味著這個原型本身有一個內部指標指向另一個原型,相應的另一個原型也有一個指標指向另一個建構式,這樣就在實體和原型之間構造了一條原型鏈,這就是原型鏈的基本思想,
實作原型鏈涉及如下代碼模式:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = https://www.cnblogs.com/smileZAZ/archive/2023/05/04/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
?以上的代碼定義了兩個型別:SuperType和SubType,這兩個型別分別定義了一個屬性和方法,兩個型別的主要區別是:SubType通過創建SuperType的實體并將其賦值給自己的原型SubType.prototype實作了對SuperType的繼承,這個賦值重寫了SubType最初的原型,將其替換成SuperType的實體,這意味著SuperType實體可以訪問的所有屬性和方法也會存在于SubType.prototype,這樣實作繼承后,緊接著又給SuperType的實體添加了一個新的方法,最后又創建了SubType的實體并呼叫了它繼承的getSuperValue方法,
默認原型
??默認情況下,所有參考型別都繼承自Object,這也是通過原型鏈實作的,任何函式默認的原型都是一個Object的實體,這意味著這個實體有一個內部指標指向Object.prototype,這也是為什么自定義型別能夠繼承包括toString()、valueOf在內部的所有默認方法的原因,
??SubType繼承SuperType,而SuperType繼承Object,在呼叫instance.toString時,實際上呼叫的是保存在Object.prototype上的方法,
原型與繼承的關系
??原型與繼承的關系可以通過兩種方式來確定:
使用instanceof運算子:
如果一個實體的原型中出現過相應的建構式,則instanceof回傳true
console.log(instance instanceof Object); //true
console.log(instance instanceof SuperType); //true
console.log(instance instanceof SubType); //true
從技術上講,instance是Object、SuperType、SubType的實體,因為instance的原型鏈中包含這些建構式的原型,結果就是instanceof對所有這些建構式都回傳true,
使用isPrototypeOf()方法
原型鏈中的每個原型都可以呼叫這個方法,這要原型鏈中包含這個原型,這個方法就會回傳true
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance)); //true
關于方法
子類有時需要覆寫父類的方法,或者增加父類沒有的方法,為此,這些方法必須在原型賦值之后再添加到原型上,來看下面的例子:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = https://www.cnblogs.com/smileZAZ/archive/2023/05/04/function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
// 繼承SuperType
SubType.prototype = new SuperType()
// 新方法
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
// 覆寫已有的方法
SubType.prototype.getSuperValue = function(){
return false;
}
let instance = new SubType()
console.log(instance.getSuperValue()); //false
以上代碼新增兩個方法,第一個方法getSubValue()是SubType的新方法,而第二個方法getSuperValue()是原型鏈上已經存在但在這里被遮蔽的方法,;后面在SubType實體上呼叫getSuperValue()時呼叫的是這個方法,而SuperValue的實體仍然會呼叫最初的方法,重點在于上述兩個方法都是在把原型賦值為SuperType的實體之后定義的,
??另一個要理解的重點,以物件字面量方式創建原型方法,會破環之前的原型鏈,因為這樣相當于重寫原型鏈,
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = https://www.cnblogs.com/smileZAZ/archive/2023/05/04/function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
// 繼承SuperType
SubType.prototype = new SuperType()
// 通過物件字面量添加新的方法,會導致上一行無效
SubType.prototype = {
getSubValue(){
return this.subproperty;
},
someOtherMethod(){
return false;
}
}
let instance = new SubType()
console.log(instance.getSuperValue()); //instance.getSuperValue is not a function
子類的原型在被賦值為SuperType的實體后,又被一個物件字面量覆寫了,覆寫后的原型是一個Object的實體,而不再是SuperType的實體,因此,之前的原型鏈就斷了,SubType和SuperType之間也沒有關系了,
原型鏈的額問題
原先的實體屬性變成了原型屬性
??原型包含的參考值會在所有實體間共享,在使用原型實作繼承時,原型實際上變成了另一個型別的實體,
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"]
SuperType建構式定義了一個colors屬性,其中包含一個陣列(參考值),每個SuperType的實體都會有自己的colors屬性,包含自己的陣列,但是當SubType通過原型繼承SuperType后,SubType.prototype變成了SuperType的一個實體,因而也獲得了自己的colors屬性,這類似于創建了SubType.prototype.colors屬性,最終結果是,SubType的所有實體都會共享這個colors屬性,這一點通過instance1.colors上的屬性也能反應到instance2.colors上就可以看出,
子型別在實體化時不能給夫型別的建構式傳參
??我們無法在不影響所有物件實體的情況下把引數傳進父類的建構式,再加上原型中包含參考值的問題,就導致原型鏈基本不會被單獨使用,
2. 盜用建構式
??盜用建構式是用來解決原型包含參考值導致的繼承問題,基本思路:在子類建構式中呼叫父類建構式,因為函式是在特定背景關系中執行代碼的簡單物件,所以可以使用apply()和call()方法以新創建的物件背景關系執行建構式,
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"]
以上代碼展示了盜用建構式的呼叫,通過使用call()或者apply()方法,SuperType建構式在為SubType的實體創建的新物件的背景關系中執行了,這相當于新的SubType物件上運行了SuperType函式中的所有初始化代碼,結果就是每個實體都會有自己的colors屬性,
1. 優點
??可以在子類建構式中向父類建構式傳參
function SuperType(name){
this.name = name;
}
function SubType(){
// 繼承SuperType并傳參
SuperType.call(this,"Nicholas");
// 實體屬性
this.age = 29
}
let instance = new SubType()
console.log(instance.name); //Nicholas
console.log(instance.age); //29
?SuperType建構式接受一個引數name,然后將它賦值給一個屬性,在SubType建構式中呼叫SuperType建構式時傳入這個引數,實際上會在SubType的實體上定義name屬性,為確保SuperType建構式不會覆寫SubType定義的屬性,可以在呼叫父類建構式之后再給子類實體添加額外的屬性,
2. 缺點
必須在建構式中定義方法,因此函式不能重用,
子類不能訪問父類原型上定義的方法,因此所有型別只能使用建構式模式,
由于存在以上問題,所以盜用建構式基本上也不能單獨使用,
3. 組合繼承??
組合繼承也叫偽經典繼承,綜合了原型鏈和盜用建構式,將兩者的優點集中起來,基本思路是:使用原型鏈繼承原型上的屬性和方法,通過盜用建構式繼承實體屬性,這樣既可以把方法定義在原型上以實作重用,又可以讓每個實體都有自己的屬性,
/* 組合繼承 */
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)
this.age = age
}
// 繼承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function(){
console.log(this.age);
}
let instance1 = new SubType("Nicholas",29)
instance1.colors.push("blcak");
console.log(instance1.colors); //["red", "blue", "green", "blcak"]
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
SuperType建構式定義了兩個屬性name和colors,而它的原型上也定義了一個方法叫sayName(),SubType建構式呼叫了SuperType建構式,傳入name引數,然后又定義了自己的屬性age,此外,SubType.prototype也被賦值為SuperType的實體,原型賦值之后,又在這個原型上添加了新方法sayAge(),這樣就可以創建兩個SubType實體,讓這兩個實體都有自己的屬性,包括colors,同時還共享相同的方法,
??組合繼承彌補了原型鏈和盜用建構式的不足,是JavaScript中使用最多的繼承模式,而且組合繼承也保留了instanceof運算子和isPrototypeOf()方法識別合成物件的能力,
缺點:
??父類建構式會被呼叫兩次,一次是在創建子類原型時呼叫,一次是在子類建構式中呼叫,
??本質上,子類原型最終是要包含超類物件的所有實體屬性,子類建構式只要在執行時重寫自己的原型就行了,
4. 原型式繼承
??2006年Douglas Crockford的文章:《JavaScript中的原型式繼承》中介紹了一種不涉及嚴格意義上建構式的繼承方法,作者的出發點是即使不自定義型別也可以通過原型實作物件之間的資訊共享,文章給出了一個函式:
function object(o){
function F(){}
F.prototype = o;
return new F()
}
這個object方法會創建一個臨時建構式,將傳入的物件賦值給這個建構式的原型,然后回傳這個臨時型別的一個實體,本質上,object()是對傳入物件執行了一次淺復制,
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"]
ECMAScript通過增加Object.create()方法將原型式繼承的概念規范化,這個方法接受兩個引數:作為新物件原型的物件,給新物件定義額外屬性的物件(可選),在只有一個引數時,Object.create()與這里的object()方法效果類似,
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", "Barbie"]
Object.create()的第二個引數與Object.defineProperties()的第二個引數一樣:每個新增引數屬性都通過各自的描述符來描述,以這種方式添加的屬性會遮蔽原型物件上的同名屬性,
let person = {
name:"Nicholas",
friends:["Shelby",'Court','Van']
}
let anotherPerson = Object.create(person,{
name:{
value:'Greg'
}
});
console.log(anotherPerson.name); //Greg
原型式繼承非常適合不需要單獨創建建構式,但仍然需要在物件間共享資訊的場合,但要記住,屬性中包含的參考值始侄訓在相關物件間共享,跟使用原型模式時一樣的,
5. 寄生式繼承
??寄生式繼承背后的思路類似于寄生建構式和工廠模式:創建一個實作繼承的函式,以某種方式增強物件,然后回傳這個物件,基本的寄生繼承模式如下:
function createAnother(original){
let clone = object(original); //通過呼叫函式創建一個新物件
clone.sayHi = function(){ //以某種方式增強這個物件
console.log("HI");
};
return clone; //返滬這個物件
}
createAnother()函式接受一個引數,就是新物件的基準物件,這個物件original會被傳給object()函式,然后將回傳的新物件賦值給clone物件添加一個新方法sayHi(),最后回傳這個物件,
function object(o){
function F(){}
F.prototype = o;
return new F()
}
function createAnother(original){
let clone = object(original); //通過呼叫函式創建一個新物件
clone.sayHi = function(){ //以某種方式增強這個物件
console.log("Hi");
};
return clone; //返滬這個物件
}
let person = {
name:"Nicholas",
friends:["Shelby",'Court','Van']
}
let anotherPerson = createAnother(person);
anotherPerson.sayHi(); //Hi
?這個例子基于person物件回傳了一個新物件,新回傳的anotherPerson物件具有person的所有屬性和方法,還有一個新方法叫sayHi()
??寄生式繼承同樣適合主要關注物件,而不在乎型別和建構式的場景,object()函式不是寄生式繼承所必需的,任何回傳新物件的函式都可以在這里使用,
==注意:==通過寄生式繼承給物件添加函式會導致函式難以重用,與建構式模式類似,
6. 寄生式組合繼承
??通過盜用建構式繼承屬性,但使用混合式原型鏈繼承方法,基本思路是不通過呼叫父類建構式給子類原型賦值,而實取得父類原型的一個副本,說到底就是使用寄生式繼承來繼承父類原型,然后將回傳的新物件賦值給子類原型,
function inheritPrototype(subType,superType){
let prototype = object(superType.prototype); //創建物件
prototype.constructor = subType; //增強物件
subType.prototype = prototype; //賦值物件
}
這個inheritPrototype()函式實作了寄生式組合繼承的核心邏輯,這個函式接受到兩個引數:子類建構式和父類建構式,這個函式內部,第一步是創建一個父類原型的一個副本,然后,給回傳的prototype物件設定constructor屬性,解決由于重寫原型導致默認constructor丟失的問題,最后將新創建的物件賦值給子型別的原型,呼叫inheritPrototype()就可以實作前面例子中的子型別原型賦值:
function object(o){
function F(){}
F.prototype = o;
return new F()
}
function inheritPrototype(subType,superType){
let prototype = object(superType.prototype); //創建物件
prototype.constructor = subType; //增強物件
subType.prototype = prototype; //賦值物件
}
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);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
}
這里只呼叫了一次SuperType建構式,避免了SubType.prototype上不必要也用不到的屬性,因此可以說這個例子效率更高,而且,原型鏈仍然保持不變,因此,instanceof運算子和isPrototypeOf()方法正常有效,寄生式組合繼承可以說是參考型別繼承的最佳方式,
本文轉載于:
https://blog.csdn.net/qq_39200185/article/details/115134869
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/551656.html
標籤:其他
上一篇:HTML中link標簽的那些屬性
下一篇:返回列表

