美文网首页
[深入03] 继承

[深入03] 继承

作者: woow_wu7 | 来源:发表于2021-07-25 12:24 被阅读0次

    导航

    [深入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
    [深入19] 手写Promise
    [深入20] 手写函数

    [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的指向
    • 缺点:
      • 在创建子类实例的时候,不能向父类传参
      • 不能实现多继承(继承多个父类),因为是给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 】
    - 缺点:
    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指向,防止引用出错,不修改的话,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>
    
    image

    寄生组合式继承

    • 寄生组合继承:<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>
    
    image

    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的继承(借用构造函数式继承)
    - es5的借用构造函数式继承:
    - 是先创建子类的this,然后将父类的属性和方法帮到子类的this对象上
    
    
    es6的继承:
    - 是将父类实例的属性和方法添加到this上,然后用子类的构造函数修改this
    

    super关键字

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

    super作为函数

    • <font color=red>super作为函数只能用于构造函数中,表示父类的构造函数,this指向子类的实例</font>
    • 注意:
      <font color=red>super作为函数,虽然表示父类的构造函数,但返回的是子类的实例 ,即super内部的this指向的是子类的实例!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</font>
    
    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作为对象
    • <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/d8809038cd38
    川神:https://juejin.im/post/6844903780035592205#heading-0
    https://www.jianshu.com/p/a8844b28ff79

    相关文章

      网友评论

          本文标题:[深入03] 继承

          本文链接:https://www.haomeiwen.com/subject/hjobmltx.html