[深入03] 繼承

導(dǎo)航

[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運算符
[深入07] 瀏覽器緩存機制(http緩存機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex

原型鏈繼承

  • <font color=red>將子類的prototype指向父類的實例,同時修改子類的constructor讓其重新指向子類</font>,或者說:將父類的實例作為子類實例的隱式原型
  • 注意點:
    • 在修改了prototype之后,需要重新修改prototype.constructor的指向
  • 缺點:
    • 在創(chuàng)建子類實例的時候,不能向父類傳參
    • 不能實現(xiàn)多繼承(繼承多個父類),因為是給prototype直接賦值
    • 多個實例共享父類的屬性和父類原型上的屬性,當屬性是引用類型時,子類實例間修改會相互影響【特別是數(shù)組】
    • 在子類的prototype上掛屬性和方法,必須在修改子類的prototype指向之后。
    • Sub.prototype = new Super('woow_wu7')之后Sub.prototype.sex = 'man',不然會被新的引用代替
原型鏈繼承
- 原理:將子類的prototype指向父類的實例,同時要修改子類的constructor屬性讓其重新指向子類
    - 因為修改了子類prototype指向父類實例后,子類的prototype.constructor就指向了父類(修改改回來,防止引用出錯)
    - 2021/07/24補充
    - 因為:具體是 ( child.constructor ) => ( Child.prototype.constructor ) => ( 父類實例的constructor,即father.constructor ) => ( Father.prototype.constructor ) => Father
    - 所以:當修改了prototype后,Child.prototype.constructor 指向了 Father,所以constructor需要重新指回Child
    - 即:修改了 【 Child.prototype = new Father() 】 之后,需要修改Child.prototype.constructor的指向 【 Child.prototype.constructor = Child 】
- 缺點:
1. 生成子類實例時,不能向父類傳參
2. 不能實現(xiàn)多繼承
3. 屬性共享,修改子類實例上的原型鏈上的引用類型的屬性時,子類實例會相互影響
4. 在子類的prototype上掛屬性和方法時,需要在子類的prototype指向父類的實例之后


代碼示例:
// 父類
function Super(name) {
  this.name = name
}
Super.prototype.age = 20

// 子類
function Sub(address) {
  this.address = address
}
Sub.prototype = new Super('woow_wu7') // 原型鏈繼承:將子類的prototype指向父類的實例,子類實例就能訪問父類實例和父類實例原型鏈上的屬性和方法,缺點:不能實現(xiàn)多繼承
Sub.prototype.constructor = Sub // 記得在修改prototype后,需要修改constructor指向,防止引用出錯,不修改的話,constructor指向了Super
Sub.prototype.sex = 'man' // 缺點:掛載屬性必須在上面步驟之后
const sub = new Sub('hangzhou') // 缺點:只能向子類傳參,不能向父類傳參

console.log(sub.address, '子類實例自身屬性')
console.log(sub.sex, '子類實例原型上的屬性')
console.log(sub.name, '子類實例原型上的屬性 => 父類實例上的屬性')
console.log(sub.age, '子類實例原型的原型上的屬性 => 父類實例原型上的屬性') // 一層層上溯


修改constructor也可以用下面的方式


Sub.prototype = Object.create(Super.prototype, { 
    // Oject.create第二個參數(shù)表示生成的原型上的屬性
    // 不要忘了重新指定構(gòu)造函數(shù) 
    constructor: {
        value: Student
    }
})

借用構(gòu)造函數(shù)繼承(經(jīng)典繼承)

  • 原理:通過call(this, 參數(shù))綁定父類中的this為子類的實例,并執(zhí)行父類,就相當于把父類中的this換成了子類實例
  • 優(yōu)點:
    • 能實現(xiàn)多繼承(即調(diào)用多個父類)
    • 生成子類實例時,可以向父類傳參
    • 繼承的屬性是直接生成在子類實例上的,各個子類實例之間修改引用類型的屬性互相不受影響
  • 缺點:
    • 不能繼承父類實例對象原型鏈上的屬性和方法
      • (因為父類沒有通過new命令生成父類實例,也沒有改變子類prototype的指向,不存在原型鏈繼承)
    • 就是構(gòu)造函數(shù)的缺點,也是優(yōu)點,作為缺點就是屬性和方法都生成在實例上,每次new都會新生成一份,造成系統(tǒng)資源浪費(即不共享屬性),對于可以共享的只讀屬性,應(yīng)該方法原型鏈上
借用構(gòu)造函數(shù)繼承


function Super1(name) {
  this.name = name
}
function Super2(age) {
  this.age = age
}
Super1.prototype.sex = 'man'

function Sub(name, age, address) {
  Super1.call(this, name) // 通過call,綁定super1的this為子類實例,并執(zhí)行Super1(),相當于 this.name = name
  Super2.call(this, age) // 優(yōu)點:可以多繼承,同時繼承了Super1和Super2中的屬性,且在子類實例上修改屬性相互不受影響
  this.address = address // 缺點:不能繼承父類實例原型鏈上的屬性和方法
}
const sub = new Sub('woow_wu7', 20, 'hangzhou') // 優(yōu)點:可以向父類傳參
console.log(sub)

組合式繼承(原型鏈繼承+借用構(gòu)造函數(shù)繼承)

  • 組合繼承:即原型鏈繼承 + 借用構(gòu)造函數(shù)繼承
  • 優(yōu)點:
    • 既具有借用構(gòu)造函數(shù)繼承的優(yōu)點(向父類傳參,多繼承,不存在屬性共享)
    • 又具有原型鏈繼承的優(yōu)點(繼承父類實例上的屬性和父類實例原型鏈上的屬性和方法,并且是共享)
  • 缺點:
    • <font color=red>會調(diào)用兩次父構(gòu)造函數(shù),導(dǎo)致子類實例和子類實例原型鏈上都有同一個屬性或方法</font>
    • <font color=red>父類被調(diào)用了兩次,一次是借用構(gòu)造函數(shù)是的call調(diào)用,一次是原型鏈繼承時的new調(diào)用</font>
    • <font color=red>因為父類兩次調(diào)用,所以子類和父類實例原型鏈上有相同的屬性和方法,造成浪費</font>
組合式繼承
- 借用構(gòu)造函數(shù)繼承 + 原型鏈繼承
- 優(yōu)點:多繼承,將父類傳參,某些屬性不共享,繼承父類實例原型鏈上的屬性和方法
- 缺點:?。。。。?! 
    - 父類被調(diào)用了兩次,一次是借用構(gòu)造函數(shù)是的call調(diào)用,一次是原型鏈繼承時的new調(diào)用
    - 因為父類兩次調(diào)用,所以子類和父類實例原型鏈上有相同的屬性和方法,造成浪費
    
    
    
代碼:
function Super1(name) {
  this.name = name
}
function Super2(age) {
  this.age = age
}
Super1.prototype.getName = function() {
  return 'Super1' + this.name
}
Super2.prototype.getAge = function() {
  return 'Super2' + this.age
}

function Sub(name, age, address) {
  Super1.call(this, name) // 借用構(gòu)造函數(shù),多繼承,但不能繼承原型鏈上的屬性
  Super2.call(this, age)
  this.address = address
}
Sub.prototype = new Super1() 
// 注意:這里沒有傳參,在原型鏈繼承這條線上,父類實例上的nane屬性是undefined
// 注意:原型鏈繼承這條線,還是不能多繼承,(如不能同時繼承Super1和Super2所在的prototye)因為是直接賦值
Sub.prototype.constructor = Sub // 記得修改constructor指向,重新指回Sub,不然會指向Super1
Sub.prototype.getAddress = function() {
  return 'Sub' + this.address
}

const sub = new Sub('woow_wu7', 20, 'hangzhou')
console.log(sub)



組合繼承最大的缺點:
1. 父類執(zhí)行了兩次
  - 1. 在new Sub('woow_wu7', 20, 'hangzhou')是會執(zhí)行Super.call(this, name)------- 生成一次name // 'woow_wu7'
  - 2. 在Sub.prototype = new Super1() 執(zhí)行了一次,又會生成一次name // undefined

2020/12/25復(fù)習組合式繼承

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 組合式繼承 = 借用構(gòu)造函數(shù)繼承 + 原型鏈式繼承
    // 優(yōu)點:兩者組合,相互補充
    // 缺點:
    // 1. 會調(diào)用兩次父構(gòu)造函數(shù),導(dǎo)致 (子類實例-即借用構(gòu)造函數(shù)繼承 ) 和 ( 子類實例的原型鏈上-即原型鏈繼承 ) 上都有相同的屬性和方法
    //    - 本例中:子類實例上有 superName1 屬性;子類實例的原型鏈上也有 superName1 屬性
    // 2. 父類被調(diào)用了兩次,一次是借用構(gòu)造函數(shù)是的call調(diào)用,一次是原型鏈繼承時的new調(diào)用
    // 3. 因為父類兩次調(diào)用,所以子類和父類實例原型鏈上有相同的屬性和方法,造成浪費
    function Super1(name) {
      this.superName1 = name
    }
    function Super2(name) {
      this.superName2 = name
    }
    Super1.prototype.superAge1 = 10
    Super2.prototype.superAge2 = 20

    function Sub(superName1, superName2, subName) {
      // 借用構(gòu)造函數(shù)繼承
      // 優(yōu)點:可以向父構(gòu)造函數(shù)傳參,多繼承,屬性不共享
      // 缺點:不能繼承父類prototype對象原型鏈上的屬性和方法
      Super1.call(this, superName1)
      Super2.call(this, superName2)
      this.subName = subName
    }
    // 原型鏈繼承
    // 優(yōu)點:可以繼承父類實例原型鏈上的屬性和方法,共享屬性
    // 缺點:在生成子類實例時不能向父類傳傳參,不能實現(xiàn)多繼承,繼承的屬性是引用類型時,子類實例之間修改會相互影響
    Sub.prototype = new Super1()
    Sub.prototype.constructor = Sub
    Sub.prototype.subAge = 30

    const sub = new Sub('super1', 'super2', 'sub')
    console.log('sub', sub)
    console.log('sub.superName1', sub.superName1)
    console.log('sub.superName2', sub.superName2)
    console.log('sub.subName', sub.subName)
    console.log('sub.superAge1', sub.superAge1)
    console.log('sub.subAge', sub.subAge)
  </script>
</body>
</html>
image

寄生組合式繼承

  • 寄生組合繼承:<font color=red>主要解決了在組合繼承中兩次調(diào)用父類的問題,這導(dǎo)致子類實例的自身屬性中有父類實例的屬性,子類實例的原型鏈中也有父類實例原型中的屬性</font>
寄生組合式繼承
- 主要解決:
    - 組合式繼承中,父類被多次調(diào)用,導(dǎo)致子類實例屬性和子類實例原型鏈上有相同的屬性的問題
    - 因為父類兩次被調(diào)用,call和new,構(gòu)造函數(shù)中的屬性會兩次生成,造成資源的浪費
    
    
function Super(name) {
  this.name = name
}
Super.prototype.getName = function() {
  return 'Super' + this.name
}
function Sub(name, age) {
  Super.call(this, name) // 借用構(gòu)造函數(shù)
  this.age = age
}
// Sub.prototype = new Super() ---------------- 原型鏈繼承,(沒用寄生組合繼承之前,即沒有使用過渡函數(shù)Parasitic)
function Parasitic(){}
Parasitic.prototype = Super.prototype
Sub.prototype = new Parasitic() 
// Parasitic內(nèi)沒有任何屬性
// 這樣就沒有執(zhí)行父類(Super構(gòu)造函數(shù)),而是間接的只繼承了父類實例原型上的屬性
Sub.prototype.constructor = Sub // 修改prototype要同時修改conscrutor指向
Sub.prototype.getAge = function() {
  return 'Sub' + this.age
}
const sub = new Sub('woow_wu7', 20)
console.log(sub)

2020/12/25復(fù)習寄生組合繼承

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 寄生組合式繼承
    function Super1(name) {
      this.superName1 = name
    }
    function Super2(name) {
      this.superName2 = name
    }
    Super1.prototype.superAge1 = 20
    function Sub(superName1, superName2, name) {
      Super1.call(this, superName1)
      Super2.call(this, superName2)
      this.subName = name
    }
    function Parasitic() { } // 中間函數(shù),本身沒有任何屬性和方法
    Parasitic.prototype = Super1.prototype
    // 這樣 sub 實例就能繼承 Super1.prototype上的屬性和方法,而這條繼承線不用在繼承 super1 實例上的方法
    Sub.prototype = new Parasitic()
    Sub.prototype.constructor = Sub
    Sub.prototype.subAge = 30
    const sub = new Sub('super1', 'super2', 'sub')
    console.log('sub', sub)
  </script>
</body>
</html>
image

class

  • class可以通過 <font color=red>extends</font>關(guān)鍵字實現(xiàn)繼承
  • 子類必須在constructor方法中調(diào)用<font color=red>super方法</font>,否在新建實例時會報錯
    • 因為子類的this需要通過父類的構(gòu)造函數(shù)獲取,不調(diào)用super方法就得不到this對象
  • 子類沒有定義constructor會被默認添加
  • <font color=red>在子類的constructor中必須先調(diào)用super()后才能使用this</font>
  • <font color=red>父類的靜態(tài)方法也會被子類所繼承</font>

es5的繼承(借用構(gòu)造函數(shù)式繼承)
- es5的借用構(gòu)造函數(shù)式繼承:
- 是先創(chuàng)建子類的this,然后將父類的屬性和方法幫到子類的this對象上


es6的繼承:
- 是將父類實例的屬性和方法添加到this上,然后用子類的構(gòu)造函數(shù)修改this

super關(guān)鍵字

  • 可以作為函數(shù),也可以作為對象

super作為函數(shù)

  • <font color=red>super作為函數(shù)只能用于構(gòu)造函數(shù)中,表示父類的構(gòu)造函數(shù),this指向子類的實例</font>
  • 注意:
    <font color=red>super作為函數(shù),雖然表示父類的構(gòu)造函數(shù),但返回的是子類的實例 ,即super內(nèi)部的this指向的是子類的實例?。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。?!</font>

super作為函數(shù)
- super作為函數(shù):只能用于構(gòu)造函數(shù)中,表示父類的構(gòu)造函數(shù)
- super作為函數(shù):內(nèi)部的this指向的是子類的實例


class A {
  constructor() {
    console.log(this, 'this')
  }
}
class B extends A {
  constructor() {
    super() // 注意:super最為函數(shù),只能用于構(gòu)造函數(shù)中表示父類的構(gòu)造函數(shù),內(nèi)部this指向子類的實例
  }
}

new B() // B this ========> super作為函數(shù),內(nèi)部this指向子類的實例

super作為對象

  • super作為對象
  • <font color=red>在普通方法中:指向父類的原型,this指向當前子類的實例</font>(實例上的屬性和方法無法通過該super獲取)
  • <font color=red>在靜態(tài)方法中:指向父類,this指向子類</font>


由于this指向子類實例,所以如果通過super對某個屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3; // !?。。?!super對某個屬性賦值,super就表示this,即子類的實例?。。。?!
    console.log(super.x); // undefined // super在普通函數(shù)中是對象時,表示父類的原型
    console.log(this.x); // 3
  }
}

let b = new B();


super作為對象,在靜態(tài)方法中:表示父類

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg); // super作為對象,在靜態(tài)方法中,表示父類,調(diào)用父類的靜態(tài)方法myMethod
  }
  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); 
// static 1
// Child.myMethod()是調(diào)用Child的靜態(tài)方法,靜態(tài)方法中的super對象表示父類

var child = new Child();
child.myMethod(2); 
// instance 2
// 實例上調(diào)用myMethod,沒構(gòu)造函數(shù)中沒有,就去原型上查找,super對象在普通方法中表示父類的原型

super總結(jié)

  • <font color=red>super作為函數(shù),表示父類的構(gòu)造函數(shù),this指向子類實例(此時this只能用于)</font>
  • <font color=red>super作為對象,在普通方法中,表示父類的原型,this指向子類實例</font></font>
  • <font color=red>super作為對象,在靜態(tài)方法中,表示父類,this指向子類</font>

es6繼承

  • class作為構(gòu)造函數(shù)的語法糖,同時具有__proto__ 和 prototype
  • 所以 class 同時具有兩條繼承鏈:
    • 子類的__proto__總是指向父類(表示構(gòu)造函數(shù)的繼承)
    • 子類的prototype.__proto__總是指向父類的prototype(表示方法的繼承)
圖片來源于網(wǎng)絡(luò)

我的簡書:http://m.itdecent.cn/p/d8809038cd38
川神:https://juejin.im/post/6844903780035592205#heading-0
http://m.itdecent.cn/p/a8844b28ff79

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容