3

[深入03] 继承

 2 years ago
source link: https://segmentfault.com/a/1190000040532495
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[深入03] 继承

发布于 29 分钟前

[[深入01] 执行上下文](https://juejin.im/post/684490...
[[深入02] 原型链](https://juejin.im/post/684490...
[[深入03] 继承](https://juejin.im/post/684490...
[[深入04] 事件循环](https://juejin.im/post/684490...
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...
[[深入08] 前端安全](https://juejin.im/post/684490...
[[深入09] 深浅拷贝](https://juejin.im/post/684490...
[[深入10] Debounce Throttle](https://juejin.im/post/684490...
[[深入11] 前端路由](https://juejin.im/post/684490...
[[深入12] 前端模块化](https://juejin.im/post/684490...
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...
[[深入14] canvas](https://juejin.im/post/684490...
[[深入15] webSocket](https://juejin.im/post/684490...
[[深入16] webpack](https://juejin.im/post/684490...
[[深入17] http 和 https](https://juejin.im/post/684490...
[[深入18] CSS-interview](https://juejin.im/post/684490...
[[深入19] 手写Promise](https://juejin.im/post/684490...
[[深入20] 手写函数](https://juejin.im/post/684490...

[[react] Hooks](https://juejin.im/post/684490...

[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...

原型链继承

  • <font color=red>将子类的prototype指向父类的实例,同时修改子类的constructor让其重新指向子类</font>,或者说:将父类的实例作为子类实例的隐式原型
    • 在修改了prototype之后,需要重新修改prototype.constructor的指向
    • 在创建子类实例的时候,不能向父类传参
    • 不能实现多继承(继承多个父类),因为是给prototype直接赋值
    • 多个实例共享父类的属性和父类原型上的属性,当属性是引用类型时,子类实例间修改会相互影响【特别是数组】
    • 在子类的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 】
  • 生成子类实例时,不能向父类传参
  • 不能实现多继承
  • 属性共享,修改子类实例上的原型链上的引用类型的属性时,子类实例会相互影响
  • 在子类的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指向,防止引用出错,不修改的话,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第二个参数表示生成的原型上的属性
    // 不要忘了重新指定构造函数
    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)

组合式继承(原型链继承+借用构造函数继承)

  • 组合继承:即原型链继承 + 借用构造函数继承
    • 既具有借用构造函数继承的优点(向父类传参,多继承,不存在属性共享)
    • 又具有原型链继承的优点(继承父类实例上的属性和父类实例原型链上的属性和方法,并且是共享)
    • <font color=red>会调用两次父构造函数,导致子类实例和子类实例原型链上都有同一个属性或方法</font>
    • <font color=red>父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用</font>
    • <font color=red>因为父类两次调用,所以子类和父类实例原型链上有相同的属性和方法,造成浪费</font>
组合式继承
- 借用构造函数继承 + 原型链继承
- 优点:多继承,将父类传参,某些属性不共享,继承父类实例原型链上的属性和方法
- 缺点:!!!!!! 
    - 父类被调用了两次,一次是借用构造函数是的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.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. 父类执行了两次
  - 1. 在new Sub('woow_wu7', 20, 'hangzhou')是会执行Super.call(this, name)------- 生成一次name // 'woow_wu7'
  - 2. 在Sub.prototype = new Super1() 执行了一次,又会生成一次name // undefined

2020/12/25复习组合式继承

<!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>
    // 组合式继承 = 借用构造函数继承 + 原型链式继承
    // 优点:两者组合,相互补充
    // 缺点:
    // 1. 会调用两次父构造函数,导致 (子类实例-即借用构造函数继承 ) 和 ( 子类实例的原型链上-即原型链继承 ) 上都有相同的属性和方法
    //    - 本例中:子类实例上有 superName1 属性;子类实例的原型链上也有 superName1 属性
    // 2. 父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用
    // 3. 因为父类两次调用,所以子类和父类实例原型链上有相同的属性和方法,造成浪费
    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) {
      // 借用构造函数继承
      // 优点:可以向父构造函数传参,多继承,属性不共享
      // 缺点:不能继承父类prototype对象原型链上的属性和方法
      Super1.call(this, superName1)
      Super2.call(this, superName2)
      this.subName = subName
    }
    // 原型链继承
    // 优点:可以继承父类实例原型链上的属性和方法,共享属性
    // 缺点:在生成子类实例时不能向父类传传参,不能实现多继承,继承的属性是引用类型时,子类实例之间修改会相互影响
    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>

寄生组合式继承

  • 寄生组合继承:<font color=red>主要解决了在组合继承中两次调用父类的问题,这导致子类实例的自身属性中有父类实例的属性,子类实例的原型链中也有父类实例原型中的属性</font>

  • 主要解决:

    • 组合式继承中,父类被多次调用,导致子类实例属性和子类实例原型链上有相同的属性的问题
    • 因为父类两次被调用,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.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复习寄生组合继承

<!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() { } // 中间函数,本身没有任何属性和方法
    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>

class

  • class可以通过 <font color=red>extends</font>关键字实现继承
  • 子类必须在constructor方法中调用<font color=red>super方法</font>,否在新建实例时会报错

    • 因为子类的this需要通过父类的构造函数获取,不调用super方法就得不到this对象
  • 子类没有定义constructor会被默认添加
  • <font color=red>在子类的constructor中必须先调用super()后才能使用this</font>
  • <font color=red>父类的静态方法也会被子类所继承</font>

  • es5的借用构造函数式继承:
  • 是先创建子类的this,然后将父类的属性和方法帮到子类的this对象上

    es6的继承:

  • 是将父类实例的属性和方法添加到this上,然后用子类的构造函数修改this

super关键字

  • 可以作为函数,也可以作为对象

super作为函数

  • <font color=red>super作为函数只能用于构造函数中,表示父类的构造函数,this指向子类的实例</font>
  • 注意:
    <font color=red>super作为函数,虽然表示父类的构造函数,但返回的是子类的实例 ,即super内部的this指向的是子类的实例!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</font>

  • 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作为对象
  • <font color=red>在普通方法中:指向父类的原型,this指向当前子类的实例</font>(实例上的属性和方法无法通过该super获取)
  • <font color=red>在静态方法中:指向父类,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在普通函数中是对象时,表示父类的原型
      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总结

  • <font color=red>super作为函数,表示父类的构造函数,this指向子类实例(此时this只能用于)</font>
  • <font color=red>super作为对象,在普通方法中,表示父类的原型,this指向子类实例</font></font>
  • <font color=red>super作为对象,在静态方法中,表示父类,this指向子类</font>

es6继承

  • class作为构造函数的语法糖,同时具有__proto__ 和 prototype
  • 所以 class 同时具有两条继承链:

    • 子类的__proto__总是指向父类(表示构造函数的继承)
    • 子类的prototype.__proto__总是指向父类的prototype(表示方法的继承)

图片来源于网络

我的简书:https://www.jianshu.com/p/d88...
川神:https://juejin.im/post/684490...
https://www.jianshu.com/p/a88...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK