js继承的几种方式及优缺点

  1. 继承方式
    1. 原型链继承
      利用cat.prototype = new Animal(), 连通了子类(cat)-子类原型(cat.prototype)-父类(animal)
      // 创建父类
      function Animal(){
          this.name = 'Animal'
      }
      
      // 添加共享方法eat
      Animal.prototype.eat = function(){
          console.log(`${this.name} eat food!`)
      }
      
      // 创建子实例
      function Cat(){
          this.size = 'big'
      }
      
      // 实现继承
      Cat.prototype = new Animal()
      
      // 添加子类的方法
      Cat.prototype.sleep = function(){
          console.log(`${this.size} ${this.name} sleep  3 hours!`)
      }
      
      // 创建实例
      let myCat = new Cat()
      
      // 调用构造函数的方法
      myCat.eat()
      
      // 调用父类方法
      myCat.sleep()

      js继承的几种方式及优缺点_第1张图片
      缺点: 引用类型的属性被所有实例共享, 修改堆内存数据时候会将所有实例的数据都修改,但是直接创建新的存储对象就不会产生此错误

      // 创建父类
      function Animal(){
          this.name = ['Animal']
        
      }
      
      // 创建子实例
      function Cat(){
          this.size = 'big'
      }
      
      // 实现继承
      Cat.prototype = new Animal()
      
      // 创建实例
      let myCat = new Cat()
      let youCat = new Cat()
      
      // 修改对象存储类型的对内存数值
      youCat.name.push('cat')
      console.log(youCat.name ) // ["Animal", "cat"]
      console.log(myCat.name )  // ["Animal", "cat"]
      
      // 修改对象存储类型的引用地址
      youCat.name = ['cat']
      console.log(youCat.name ) // ["cat"]
      console.log(myCat.name )  // ["Animal", "cat"]
      
      function Animal(){
        this.sleep = [1,2,3,4]
      }
      function cat (){
      }
      cat.prototype.run = '20km'
      // 原型链继承
      cat.prototype = new Animal()
      let mao1 = new cat()
      let mao2 = new cat()
      console.log(mao1.sleep) //  [1,2,3,4]
      mao1.sleep.push(...[7,8,9])
      // 构造函数的引用类型被修改后其余对象实例都受到影响
      console.log(mao1.sleep) // [1,2,3,4,7,8,9]
      console.log(mao2.sleep) // [1,2,3,4,7,8,9]
      
      // 子类的prototype的constructor指向了构造函数,所以子类的构造方法不会被子类实例对象继承
      console.log(mao1.run) //undefined
      
      // 解决办法,将子类的prototype的constructor指向子类
      cat.prototype.constructor = cat
      cat.prototype.jump = '10km'
      // 子类的原型上绑定的方法必须要在修改原型的constructor之后
      console.log(mao1.run) // unfined
      console.log(mao1.jump) // 10km
      


      js继承的几种方式及优缺点_第2张图片
      如果子对象的方法和构造函数的方法相同,子对象的对象实例,会调用子对象的方法,构造函数中的属性将被隐蔽,可以使用__prorto__调用

      function Animal(){
        this.sleep = [1,2,3,4]
        this.eat = function(){
              console.log('Animal eat food')
          }
      }
      function cat (){
          this.eat = function(){
              console.log('cat eat finsh')
          }
      }
      // 原型链继承
      cat.prototype = new Animal()
      let mao1 = new cat()
      console.log(mao1.eat()) // cat eat finsh


      js继承的几种方式及优缺点_第3张图片
      js继承的几种方式及优缺点_第4张图片

    2. 盗用构造函数继承
      function Animal(){
        this.sleep = [1,2,3,4]
        this.eat = function(){
              console.log('Animal eat food')
          }
      }
      function cat (){
      // 盗用构造函数
        Animal.call(this)
      }
      let mao = new cat()
      console.log(mao)

      js继承的几种方式及优缺点_第5张图片
      1.创建子类实例时候,都会调用构造函数,复制一份构造函数.影响性能
      2.只能继承构造函数的属性和方法。不能继承构造函数的原型
      function Animal(){
        this.sleep = [1,2,3,4]
        this.eat = function(){
              console.log('Animal eat food')
          }
      }
      Animal.prototype.jump = function (){ console.log('Animal jump 20km')}
      
      function cat (){
      // 盗用构造函数
        Animal.call(this)
      }
      cat.prototype.run = function (){ console.log('cat run 10km')}
      let mao = new cat()
      console.dir(mao.run())
      console.dir(mao.jump())

      js继承的几种方式及优缺点_第6张图片
    3. 组合继承
      将原型链继承和借用构造函数继承组合在一起的继承
      function Animal(){
          this.coler = 'red'
          this.age = 2
          this.sleep = [1,2,3,4]
      }
      Animal.prototype.run = function (){ console.log('Animal run 10km')}
      
      function Cat(){
          // 盗用构造函数
          Animal.call(this)
          this.name = {first:'big',last:'mao'}
      }
      // 原型链继承
      Cat.prototype = new Animal()
      Cat.prototype.constructor = cat
      
      Cat.prototype.eat = function (){console.log('cat eat fish')}
      
      // 创建实例
      let mao = new Cat()
      let mao1 = new Cat()
      mao.run() // 构造函数的原型 ,被继承
      mao.sleep.push(...[8,9])// 修改构造函数的对象类型属性,没有相互影响
      console.log(mao.sleep)
      console.log(mao1.sleep)
      mao.eat() // 调用对象的原型方法,也被继承了

      js继承的几种方式及优缺点_第7张图片
      缺点继承了双份一样的构造函数的属性和方法
      js继承的几种方式及优缺点_第8张图片

    4. 原型式继承
      利用一个空对象作为中介,将空对象的原型指向传入的对象,返回空对象的对象实例
      function getObj(obj){
          function F(){}
          F.prototype = obj
          return new F()
      }
      let obj = {name:'zhangsan', age:['20',5],game:function(){}}
      let f1 = getObj(obj)
      let f2 = getObj(obj)
      f1.age.push(78)
      console.log(f1.age) //["20", 5, 78]
      console.log(f1.age) //["20", 5, 78]
      缺点是多个实例对象之间会将传入的对象的引用属性篡改,并且无法传参
      js继承的几种方式及优缺点_第9张图片
    5. 寄生式继承
      就是创建一个方法,调用原型式继承赋值给一个对象,然后给对象添加一些方法,再返回对象,目的是给构造函数新增属性和方法已增强函数
      function getObj(obj){
          function F(){}
          F.prototype = obj
          return new F()
      }
      function getOther(obj){
          let p = getObj(obj)
          p.say= function(){console.log('say hello ')}
          return p
      }
      let obj = {name:'zhangsan', age:['20',5],game:function(){}}
      let other = getOther(obj)
      let other1 = getOther(obj)
      other.age.push(92)
      console.log(other.age)
      console.log(other1.age)
      other1.say()
      原型式继承有的缺点寄生式继承也存在
      js继承的几种方式及优缺点_第10张图片
    6. 寄生组合式继承
      使用盗用构造函数方式,将父类的属性方法放入子类
      通过一个函数将父类的原型复制对象指向子类,将子类的原型指向复制的这个对象
      最后可以获取父类的属性和方法的复制对象(不会相互影响),也不会存放两次父类的属性和方法
      // 构造函数的构成
      function inheritPrototype(subType, superType){
      //     复制一个父类原型
          copySuperPrototype = Object.create(superType.prototype)
      //     将父类原型的构造函数执行子类,弥补因重写原型而失去的默认的constructor 属性
          copySuperPrototype.constructor = subType
      // 将子类的原型指向父类复制出来的原型上
          subType.prototype = copySuperPrototype
      }
      // 父类
      function superType(name){
          this.name = name
          this.color = ['red']
      }
      // 子类
      function subType(name, age){
          superType.call(this,name)
          this.age = age
      
      }
      inheritPrototype(subType, superType)
      let o1 = new subType('zhangsan',12)
      let o2 = new subType('lisi',78)
      
      o1.color.push(1)
      console.dir(o1)
      console.dir(o2)
      这是现在库实现的方法,也是目前最成熟的方法
      js继承的几种方式及优缺点_第11张图片
    7. 混入方式继承多个对象
      Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法
      function MyClass() {
           SuperClass.call(this);
           OtherSuperClass.call(this);
      }
      
      // 继承一个类
      MyClass.prototype = Object.create(SuperClass.prototype);
      // 混合其它
      Object.assign(MyClass.prototype, OtherSuperClass.prototype);
      // 重新指定constructor
      MyClass.prototype.constructor = MyClass;
      
      MyClass.prototype.myMethod = function() {
           // do something
      };

       

    8. ES6类继承extends
      extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。
      class Rectangle {
          // constructor
          constructor(height, width) {
              this.height = height;
              this.width = width;
          }
          
          // Getter
          get area() {
              return this.calcArea()
          }
          
          // Method
          calcArea() {
              return this.height * this.width;
          }
      }
      
      const rectangle = new Rectangle(10, 20);
      console.log(rectangle.area);
      // 输出 200
      
      -----------------------------------------------------------------
      // 继承
      class Square extends Rectangle {
      
        constructor(length) {
          super(length, length);
          
          // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
          this.name = 'Square';
        }
      
        get area() {
          return this.height * this.width;
        }
      }
      
      const square = new Square(10);
      console.log(square.area);
      // 输出 100
      extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
      
      function _inherits(subType, superType) {
        
          // 创建对象,创建父类原型的一个副本
          // 增强对象,弥补因重写原型而失去的默认的constructor 属性
          // 指定对象,将新创建的对象赋值给子类的原型
          subType.prototype = Object.create(superType && superType.prototype, {
              constructor: {
                  value: subType,
                  enumerable: false,
                  writable: true,
                  configurable: true
              }
          });
          
          if (superType) {
              Object.setPrototypeOf 
                  ? Object.setPrototypeOf(subType, superType) 
                  : subType.__proto__ = superType;
          }
      }

       

    9. class继承(es6中的class继承,本质是原型链)

你可能感兴趣的