[深刻03] 繼承

導航

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

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

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

原型鏈繼承

  • 將子類的prototype指向父類的實例,同時修改子類的constructor讓其從新指向子類
  • 或者說:將父類的實例做爲子類實例的隱式原型
  • 缺點:
    • 在建立子類實例的時候,不能向父類傳參
    • 不能實現多繼承(繼承多個父類),由於是給prototype直接賦值
    • 多個實例共享父類的屬性和父類原型上的屬性,當屬性是引用類型時,子類實例間修改會相互影響【特別是數組】
    • 在子類的prototype上掛屬性和方法,必須在修改子類的prototype指向以後。
    • Sub.prototype = new Super('woow_wu7')以後Sub.prototype.sex = 'man',否則會被新的引用代替
原型鏈繼承
- 原理:將子類的prototype指向父類的實例,同時要修改子類的constructor屬性讓其從新指向子類
    - 由於修改了子類prototype指向父類實例後,子類的prototype.constructor就指向了父類(修改改回來,防止引用出錯)
- 缺點:
1. 生成子類實例時,不能向父類傳參
2. 不能實現多繼承
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指向父類的實例,子類實例就能訪問父類實例和父類實例原型鏈上的屬性和方法,缺點:不能實現多繼承
Sub.prototype.constructor = Sub // 記得在修改prototype後,須要修改constructor指向,防止引用出錯
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第二個參數表示生成的原型上的屬性
    // 不要忘了從新指定構造函數 
    constructor: {
        value: Student
    }
})
複製代碼

借用構造函數繼承(經典繼承)

  • 原理:經過call(this, 參數)綁定父類中的this爲子類的實例,並執行父類,就至關於把父類中的this換成了子類實例
  • 優勢:
    • 能實現多繼承(即調用多個父類)
    • 生成子類實例時,能夠向父類傳參
    • 繼承的屬性是直接生成在子類實例上的,各個子類實例之間修改引用類型的屬性互相不受影響
  • 缺點:
    • 不能繼承父類實例對象原型鏈上的屬性和方法
      • (由於父類沒有經過new命令生成父類實例,也沒有改變子類prototype的指向,不存在原型鏈繼承)
    • 就是構造函數的缺點,也是優勢,做爲缺點就是屬性和方法都生成在實例上,每次new都會新生成一份,形成系統資源浪費(即不共享屬性),對於能夠共享的只讀屬性,應該方法原型鏈上
借用構造函數繼承


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爲子類實例,並執行Super1(),至關於 this.name = name
  Super2.call(this, age) // 優勢:能夠多繼承,同時繼承了Super1和Super2中的屬性,且在子類實例上修改屬性相互不受影響
  this.address = address // 缺點:不能繼承父類實例原型鏈上的屬性和方法
}
const sub = new Sub('woow_wu7', 20, 'hangzhou') // 優勢:能夠向父類傳參
console.log(sub)
複製代碼

組合式繼承(原型鏈繼承+借用構造函數繼承)

  • 組合繼承:即原型鏈繼承 + 借用構造函數繼承
  • 優勢:
    • 既具備借用構造函數繼承的優勢(向父類傳參,多繼承,不存在屬性共享)
    • 又具備原型鏈繼承的優勢(繼承父類實例原型鏈上的屬性和方法,而且是共享)
  • 缺點:
    • 會調用兩次父構造函數,致使子類實例和子類實例原型鏈上都有同一個屬性或方法
    • 父類被調用了兩次,一次是借用構造函數是的call調用,一次是原型鏈繼承時的new調用
    • 由於父類兩次調用,因此子類和父類實例原型鏈上有相同的屬性和方法,形成浪費
組合式繼承
- 借用構造函數繼承 + 原型鏈繼承
- 優勢:多繼承,將父類傳參,某些屬性不共享,繼承父類實例原型鏈上的屬性和方法
- 缺點:!!!!!! 
    - 父類被調用了兩次,一次是借用構造函數是的call調用,一次是原型鏈繼承時的new調用
    - 由於父類兩次調用,因此子類和父類實例原型鏈上有相同的屬性和方法,形成浪費
    
    
    
代碼:
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) // 借用構造函數,多繼承,但不能繼承原型鏈上的屬性
  Super2.call(this, age)
  this.address = address
}
Sub.prototype = new Super1() 
// 注意:這裏沒有傳參,在原型鏈繼承這條線上,父類實例上的nane屬性是undefined
// 注意:原型鏈繼承這條線,仍是不能多繼承,(如不能同時繼承Super1和Super2所在的prototye)由於是直接賦值
Sub.constructor = Sub // 記得修改constructor指向
Sub.prototype.getAddress = function() {
  return 'Sub' + this.address
}

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



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

寄生組合式繼承

  • 寄生組合繼承:主要解決了在組合繼承中兩次調用父類的問題,這致使子類實例的自身屬性中有父類實例的屬性,子類的原型鏈中也有父類實例原型中的屬性
寄生組合式繼承
- 主要解決:
    - 組合式繼承中,父類被屢次調用,致使子類實例屬性和子類實例原型鏈上有相同的屬性的問題
    - 由於父類兩次被調用,call和new,構造函數中的屬性會兩次生成,形成資源的浪費
    
    
function Super(name) {
  this.name = name
}
Super.prototype.getName = function() {
  return 'Super' + this.name
}
function Sub(name, age) {
  Super.call(this, name) // 借用構造函數
  this.age = age
}
// Sub.prototype = new Super() ---------------- 原型鏈繼承,(沒用寄生組合繼承以前,即沒有使用過渡函數Parasitic)
function Parasitic(){}
Parasitic.prototype = Super.prototype
Sub.prototype = new Parasitic() 
// Parasitic內沒有任何屬性
// 這樣就沒有執行父類(Super構造函數),而是間接的只繼承了父類實例原型上的屬性
Sub.constructor = Sub // 修改prototype要同時修改conscrutor指向
Sub.prototype.getAge = function() {
  return 'Sub' + this.age
}
const sub = new Sub('woow_wu7', 20)
console.log(sub)
複製代碼

class

  • class能夠經過 extends關鍵字實現繼承
  • 子類必須在constructor方法中調用super方法,否在新建實例時會報錯
    • 由於子類的this須要經過父類的構造函數獲取,不調用super方法就得不到this對象
  • 子類沒有定義constructor會被默認添加
  • 在子類的constructor中必須先調用super()後才能使用this
  • 父類的靜態方法也會被子類所繼承
es5的繼承(借用構造函數式繼承)
- es5的借用構造函數式繼承:
- 是先建立子類的this,而後將父類的屬性和方法幫到子類的this對象上


es6的繼承:
- 是將父類實例的屬性和方法添加到this上,而後用子類的構造函數修改this
複製代碼

super關鍵字

  • 能夠做爲函數,也能夠做爲對象

super做爲函數

  • super做爲函數只能用於構造函數中,表示父類的構造函數,this指向子類的實例
  • 注意:
    super做爲函數,雖然表示父類的構造函數,但返回的是子類的實例 ,即super內部的this指向的是子類的實例!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
super做爲函數
- super做爲函數:只能用於構造函數中,表示父類的構造函數
- super做爲函數:內部的this指向的是子類的實例


class A {
  constructor() {
    console.log(this, 'this')
  }
}
class B extends A {
  constructor() {
    super() // 注意:super最爲函數,只能用於構造函數中表示父類的構造函數,內部this指向子類的實例
  }
}

new B() // B this ========> super做爲函數,內部this指向子類的實例
複製代碼

super做爲對象

  • super做爲對象
  • 在普通方法中:指向父類的原型,this指向當前子類的實例(實例上的屬性和方法沒法經過該super獲取)
  • 在靜態方法中:指向父類,this指向子類
因爲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在普通函數中是對象時,表示父類的原型
    console.log(this.x); // 3
  }
}

let b = new B();
複製代碼
super做爲對象,在靜態方法中:表示父類

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做爲對象,在靜態方法中,表示父類,調用父類的靜態方法myMethod
  }
  myMethod(msg) {
    super.myMethod(msg);
  }
}

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

var child = new Child();
child.myMethod(2); 
// instance 2
// 實例上調用myMethod,沒構造函數中沒有,就去原型上查找,super對象在普通方法中表示父類的原型
複製代碼

super總結

  • super做爲函數,表示父類的構造函數,this指向子類實例(此時this只能用於)
  • super做爲對象,在普通方法中,表示父類的原型,this指向子類實例
  • super做爲對象,在靜態方法中,表示父類,this指向子類

es6繼承

  • class做爲構造函數的語法糖,同時具備__proto__ 和 prototype
  • 因此 class 同時具備兩條繼承鏈:
    • 子類的__proto__老是指向父類(表示構造函數的繼承)
    • 子類的prototype.__proto__老是指向父類的prototype(表示方法的繼承)

圖片來源於網絡

個人簡書:www.jianshu.com/p/d8809038c…
川神:juejin.im/post/5c433e…
www.jianshu.com/p/a8844b28f…webpack

相關文章
相關標籤/搜索