一、原型鏈繼承
構造函數、原型和實例的關系: 每個構造函數都有一個原型對象,原型有一個屬性指回構造函數,而實例有一個內部指針指向原型。
原型鏈的基本構想: 如果原型是另一個類型的實例呢?那就意味著這個原型本身有一個內部指針指向另一個原型,相應地另一個原型也有一個指針指向另一個構造函數。這樣就在實例和原型之間構造了一條原型鏈。
重點: 讓新實例的原型等于父類的實例。
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
復制代碼
特點:
- 實例可繼承的屬性有:實例的構造函數的屬性,父類構造函數屬性,父類原型的屬性。
缺點:
- 新實例無法向父類構造函數傳參。
- 繼承單一。(只能繼承一個父類構造函數)
- 所有新實例都會共享父類實例的屬性。(原型上的屬性是共享的,一個實例修改了原型屬性,另一個實例的原性也會被修改!)
- 要想為子類原型新增屬性和方法,必須要在new 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"
復制代碼
二、借用構造函數繼承
重點: 用.call()和.apply()將父類構造函數引入子類函數(在子類函數中做了父類函數的自執行(復制))
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
復制代碼
特點:
- 只繼承了父類構造函數的屬性,沒有繼承父類原型的屬性。
- 解決了原型鏈繼承缺點 1、2、3。
- 可以繼承多個構造函數屬性(call 多個)。
- 在子實例中可向父實例傳參。
- 解決了引用值問題
缺點:
- 只能繼承父類構造函數的屬性。
- 無法實現構造函數的復用。
- 每個新實例都有父類構造函數的副本,臃腫。
三、組合繼承(組合原型鏈繼承和借用構造函數繼承)(常用)
重點: 結合了兩種模式的優點,傳參和復用
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) //// 第一次調用 SuperType()
this.age = age
}
// 繼承方法
SubType.prototype = new SuperType() // 第二次調用 SuperType()
SubType.prototype.sayAge = function () {
console.log(this.age)
}
let instance1 = new SubType("Nicholas", 29)
console.log("instance1=>", instance1)
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
復制代碼
特點:
- 可以繼承父類原型上的屬性,可以傳參,可復用。
- 每個新實例引入的構造函數屬性是私有的。
缺點: 組合繼承其實也存在效率問題。最主要的效率問題就是 父類構造函數始終會被調用兩次 :一次在是創建子類原型時調用,另一次是在子類構造函數中調用
四、原型式繼承
重點: 用一個函數包裝一個對象,然后返回這個函數的調用,這個函數就變成了個可以隨意增添屬性的實例或對象。object.create()就是這個原理。
//核心代碼
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"
復制代碼
特點: 類似于復制一個對象,用函數來包裝。
缺點:
- 所有實例都會繼承原型上的屬性。
- 無法實現復用。(新實例屬性都是后面添加的)
原型式繼承非常適合不需要單獨創建構造函數,但仍然需要在對象間共享信息的場合。但要記住,屬性中包含的引用值始終會在相關對象間共享,跟使用原型模式是一樣的
五、寄生式繼承
重點: 就是給原型式繼承外面套了個殼子。
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"
//寄生式繼承同樣適合主要關注對象,而不在乎類型和構造函數的場景。object()函數不是寄生式繼承所必需的,任何返回新對象的函數都可以在這里使用。
// 注意 通過寄生式繼承給對象添加函數會導致函數難以重用,與構造函數模式類似。
復制代碼
優點: 沒有創建自定義類型,因為只是套了個殼子返回對象(這個),這個函數順理成章就成了創建的新對象。
缺點: 沒用到原型,無法復用。
六、寄生組合式繼承(常用)
重點: 通過借用構造函數繼承屬性 ,但使用混合式原型鏈繼承方法。基本思路是不通過調用父類構造函數給子類原型賦值,而是取得父類原型的一個副本。說到底就是使用寄生式繼承來繼承父類原型,然后將返回的新對象賦值給子類原型。
寄生: 在函數內返回對象然后調用
組合:
- 函數的原型等于另一個實例。
- 在函數中用 apply 或者 call 引入另一個構造函數,可傳參
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
}
let prototype = object(superType.prototype) // 創建對象
subType.prototype = prototype // 賦值對象
prototype.constructor = subType // 修復實例
//inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age)
}
復制代碼
優先: 修復了組合繼承的問題
缺點: 實現麻煩
文章出自:??前端餐廳ReTech??,如有轉載本文請聯系前端餐廳ReTech今日頭條號。
github:??https://github.com/zuopf769??