javascript的寄生组合继承

246 阅读5分钟

一、前言

javascript寄生组合继承,是现有继承模式中比较完美的继承方式。和 es6classextends继承的实现几乎相同。但也存在着一些细微的差别。我们在文章结尾会提到。

二、寄生组合继承

顾名思义,寄生组合寄生组合当然是结合了寄生继承组合继承。那组合继承又是谁和谁的组合呢。这里直接公布答案。组合继承是将原型继承盗用构造函数继承两种继承方式组合在一起的继承模式。那么我们得出了最终的答案

寄生组合继承 = 寄生继承 + (原型继承 + 盗用构造函数继承)

这就是寄生组合继承的一个整体架构思想。接下来我们一个一个撸他们。

一、原型继承

我们先来看一段代码实现

    // 父构造函数方法
    function Parent() {}
    // 在父构造函数的原型上添加方法
    Parent.prototype.sayName = function() {
        console.log('sayNmae')
    }
    // 子构造函数
    function Child() {}
    // 将子构造函数的原型对象修改为父构造函数的实例对象
    Child.prototype = new Parent()
    
    const instance = new Child()
    
    instance.sayName() // sayName

在这个例子中首先定义了 ParentChild两个构造函数。然后将 Child 的prototype 修改为 Parent 的实例。这样子 Child 就可以使用 Parent 上的方法。

这种方式就是简单利用了原型链来继承。

  • 优点:简单,强大
  • 缺点:所有实例都会共享属性和方法,而且子构造函数不能向父构造函数传参。

二、盗用构造函数继承

    function Parent(name) {
        this.color = [1, 2, 3]
        this.name = name
    }
    
    function Child(name, age) {
        // 通过在子构造函数中调用父构造函数来向父构造函数传参
        Parent.call(this, name)
        this.age = age
    }
    
    const instance = new Child('huyunkun', 27)
    instance.color.push(5) // instance.color  = [1, 2, 3, 5]
    const instance2 = new Child('huyunkun1', 28) // instance2.color = [1, 2, 3]
    

以上代码通过在 Child 中调用 Parent。从而在实例化的时候也会执行 Parent。通过用 call 修改 this 的指向。

通过这种方式我们也可以实现由 ChildParent 数传参。每一个实例中的属性都是独立的。

  • 缺点:
    • 只能继承父类的属性
    • 每次实例化操作,都要重新调用父构造函数
    • 每个新的实例都有父构造函数的副本

三、组合模式

我们已经介绍了原型继承和盗用构造函数继承。我们现在通过将两种继承方式结合。实现组合继承。 先来看一段代码

    function Parent(name) {
          this.name = name
    }
    
    Parent.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function Child(name, age) {
        Parent.call(this, name)
        this.age = age
    }
    // 修改child 上的原型
    Child.prototype = new Parent()
    
    Child.prototype.sayAge = function() {
        console.log(this.age)
    }
    
    const instance = new Child('huyunkun', 27)
    

从代码上看我们依然在 Child 中执行了 Parent 从而利用了盗用构造函数的优点。 通过修改 Childprototype 指向 Parent 的实例。来利用原型继承的优点。

  • 优点
    • 我们可结合了两种继承模式,利用了两种继承模式的优点
  • 缺点
    • 这种方式调用了两次 Parent
    • 并且在 Child 的原型上会有 Parent 的属性。与 Child 实例上的属性重复

四、寄生模式

    function Child(obj) {
        const newObj = Object(obj)
        newObj.sayName = function() {
            console.log('sayName')
        }
        
        return newObj
    }
    
    const obj = {
        name: 'huyunkun',
        age: 27
    }
    
    const instance = Child(obj)

寄生继承看起来也非常简单。主要就是利用函数,对原对象进行cpoy。然后在copy出来的新对象进行加强。最后将新对象返回出来。

五、寄生组合继承

我们现在已经介绍完了组合继承,寄生继承。那么现在。我们只要将两种继承模式组合起来。就能得到寄生组合继承模式。我们一起来看看

    function Parent(name) {
        this.name = name
        this.color = [1, 2, 3]
    }
    
    Parent.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function Child(name, age) {
        Parent.call(this, name)
        this.age = age
    }
    
    function clone(child, parent) {
        const prototype = Object.create(parent.prototype)
        child.prototype = prototype
        child.prototype.constructor = Child
    }
    
    clone(Child, Parent)
    
    const instance1 = new Child('huyunkun1', 27)
    const instance2 = new Child('huyunkun2', 28)
  • clone 这个函数对标我们的寄生部分。对传入的对象进行加强
  • Child 中调用Parent函数,对标盗用构造函数部分
  • clone 中修改 Child 的原型可以对标我们的原型继承

有几个小点我们注意一下

  • clone 函数中修改完child的原型后会丢失原来的 constructor ,这里我们需要重新赋值 constructor

这种继承方式,融合了之前所提到的几种继承方式的优点。是目前来说比较完美的一种继承方式。

六、class种的extends继承

最后我们来看一看 es6 中的 extends 继承方式。其实 class 只是一种语法糖,当我们使用 babel 编译之后会发现,其实现依然是 function 。而 extends 的继承方式基本和组合寄生继承大差不差。

大家可以在babel上在线尝试代码编译

我们来看看编译后的核心代码

var Parent = /*#__PURE__*/function () {
  function Parent (name) {
    _classCallCheck(this, Parent);
    this.name = name;
  }
  // 这里主要在Parent的原型上绑定方法
  _createClass(Parent, [{
    key: "getName",
    value: function getName () {
      console.log(this.name);
    }
  }], [{
    key: "getTest",
    value: function getTest () {
      console.log(123124);
    }
  }]);
  return Parent;
}();
var Child = /*#__PURE__*/function (_Parent) {
 // 这个函数就是重点方法
  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

  function Child (name, age) {
    var _this;
    _classCallCheck(this, Child);
    _this = _super.call(this, name);
    _this.age = age;
    return _this;
  }
  _createClass(Child, [{
    key: "getAge",
    value: function getAge () {
      console.log(this.age);
    }
  }]);
  return Child;
}(Parent);

我们先来看一下大致流程。其实也很简单。构造函数都会通过 _createClass 来给原型添加方法。

  • 第一个参数会把属性挂在原型上
  • 第二个参数会把属性挂到构造函数上(主要是实现静态方法和静态属性)

核心方法是 _inherits,我们来看一下

function _inherits (subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }

subClass.prototype = Object.create(superClass && superClass.prototype,
    {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });

  Object.defineProperty(
    subClass,
    "prototype",
    { writable: false });

  if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf (o, p) {
  _setPrototypeOf = Object.setPrototypeOf ?
    Object.setPrototypeOf.bind() :
    function _setPrototypeOf (o, p) {
      o.__proto__ = p; ``
      return o;
    };
  return _setPrototypeOf(o, p);
}

通过 Object.create 方法修改了 subClass 上 的原型。同时修改了 subClass.prototype.constructorsubClass。最后痛殴_setPrototypeOf函数将 subClass.__proto__ 指向 superClass

最后我们来看一看组合寄生继承和 extends 两种继承方式原型上的对比

组合寄生继承

    console.log(Child.prototype.constructor === Child) // true
    console.log(instance.__proto__ === Child.prototype)// true
    console.log(Child.__proto__ === Function.prototype)// true
    console.log(Child.prototype.__proto__ === Parent.prototype)// true
    console.log(instance.__proto__.__proto__ === a2.__proto__)// true

    console.log(Object.__proto__ === Function.prototype)// true
    console.log(Function.prototype.__proto__ === Object.prototype)// true
    console.log(Object.prototype.__proto__ === null)// true

extends继承

    const instance1 = new Child('hyk', 27)
    const instance2 = new Parent()
    console.log(Child.prototype.constructor === Child)
    console.log(instance1.__proto__ === Child.prototype)
    console.log(Child.__proto__ === Parent)
    console.log(Child.prototype.__proto__ === Parent.prototype)
    console.log(instance1.__proto__.__proto__ === instance2.__proto__)

好了今天我们就说到这里了