深入了解JS中的寄生组合式继承

183 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

首先,寄生组合继承 是寄生继承组合继承的结合版 , 抛开它们自然无法说清楚。

(寄生组合继承是继承那一章节的浓缩精华,对新手来说完全理解是要看完整一个章节的)

组合继承:

曾经一段时间因为javascript关于类实现继承的不规范,导致各种各样实现继承的代码;而实际上不管代码怎么变,继承都基于两种方式:

1.通过原型链 即子类的原型指向父类的实例从而实现原型共享。

2.借用构造函数 即通过js的apply、call实现子类调用父类的属性、方法;

原型链方式可以实现所有属性方法共享,但无法做到属性、方法独享(例如Sub1修改了父类的函数,其他所有的子类Sub2、Sub3...想调用旧的函数就无法实现了);

而借用构造函数除了能独享属性、方法外还能在子类构造函数中传递参数,但代码无法复用。总体而言就是可以实现所有属性方法独享,但无法做到属性、方法共享(例如,Sub1新增了一个函数,然后想让Sub2、Sub3...都可以用的话就无法实现了,只能Sub2、Sub3...各自在构造函数中新增)。

组合继承就是把以上两种继承方式一起使用,把共享的属性、方法用原型链继承实现,独享的属性、方法用借用构造函数实现,所以组合继承几乎完美实现了js的继承;

组合式继承

组合式继承将原型链和构造函数的技术组合到一起,结合两者的优点,通过原型链实现对原型属性和方法的继承以及借用构造函数来实现对实例属性的继承。拥有以下的优点

  1. 父类的方法可以被复用

  2. 父类的引用属性不会被共享

  3. 子类构建实例时可以向父类传递参数
    不过组合式继承中两次调用了父类构造函数,

// 创建父类
   let parent = function(name) {
      // 父类型的自有属性
      this.name = name;
      this.hobbies = ['tennis','music','photography']
   }
   // 添加父类方法
   parent.prototype = {
       getName() { console.log(this.name) }
   }
   // 创建子类
   let son = function(name,sex) {
      //构造函数式继承父类属性,这里是第二次调用父类构造函数
      parent.call(this, name); 
      // 添加子类自己的私有属性
      this.sex = sex;
   }
   // 这里是类式继承子类原型继承父类,是第一次调用父类构造函数
   son.prototype = new parent(); 
   // 将构造函数指向自己
   son.prototype.constructor = son;
   // 添加子类自己的方法
   son.prototype.getsex = function () { console.log(this.sex)}
   // 创建实例
   let sub1 = new son('taec','male')
   console.log(sub1)
   sub1.getName()
   sub1.getsex()

image.png

根据上面代码个人理解的原型链如下图所示,如果不对希望有大佬指正下

image.png

寄生式继承

创建一个封装基础过程的函数,该函数内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

   // 设置父类自有属性和方法
   let parent2 =  {
      name:'zy',
      hobbies:['tennis','music','photography'],
      getName:function () {console.log(this.name)}
   }
   // 这个方法用于创建一个新对象并且连接原型链
   function object (obj) {
     function F(){}
     F.prototype = obj;
     return new F (); 
   }
   function createson (o,sex) { 
     // 传入父类创建个新对象  
     let newson = object(o)
     // 这里增强对象,添加属性和方法
     newson.sex = sex
     newson.getsex = function () { console.log(this.sex) }
     // 返回对象
     return newson
   }  
   let sub2 = createson(parent2,'famle')
   console.log(sub2)
   sub2.getName()
   sub2.getsex()

image.png

根据上面的代码可以建立如下的原型链图,如果不对希望有大佬指正下

寄生式继承因为使用了一个函数以某种形式来增强对象,最后返回对象,那么复用率就不高,导致效率低。

寄生组合式继承

了解了组合继承和寄生继承之后就是寄生式组合继承了,它是通过借用构造函数来继承属性,通过原型链形式来继承方法,会解决2次调用父类函数以及复用率的问题。
这里使用《JavaScript高级程序设计》中的代码来解释继承的方法

  // 实现继承的核心函数
  function inheritPrototype(subType,superType) {
     function F() {};
     //F()的原型指向的是superType
     F.prototype = superType.prototype; 
     //subType的原型指向的是F()
     subType.prototype = new F(); 
     // 重新将构造函数指向自己,修正构造函数
     subType.prototype.constructor = subType; 
  }
  // 设置父类
  function SuperType(name) {
      this.name = name;
      this.colors = ["red", "blue", "green"];
      SuperType.prototype.sayName = function () {
        console.log(this.name)
      }
  }
  // 设置子类
  function SubType(name, age) {
      //构造函数式继承--子类构造函数中执行父类构造函数
      SuperType.call(this, name);
      this.age = age;
  }
  // 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
  inheritPrototype(SubType, SuperType)
  // 添加子类私有方法
  SubType.prototype.sayAge = function () {
     console.log(this.age);
  }
  var instance = new SubType("Taec",18)
  console.dir(instance)

image.png

可以看到很好的继承了父类的方法和属性以及自己添加属性和方法,并且只调用了1次父类构造函数,同时保证了原型链的完整,是一种理想的继承方法。下图是自己理解的图示,如果有错误希望大佬来指正

参考了一些文章:

  1. 一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends
  2. 寄生组合式继承
  3. 六种Js中常见的继承方式(图解)