继承的那些事

继承是 OO 语言中一个最为津津乐道的概念,许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。因为函数没有签名,在 ECMAScript 中没法实现接口继承。ECMAScript 只支持实现继承并且实现继承主要是依靠原型链来实现的。

关于原型链,我以前的文章里面有介绍,若是有些忘记了,能够看这篇文章
下面我将详细的介绍前端前辈在开发过程当中不断摸索创造的几种继承方式。看完面试的时候千万不要简单的回答 call 跟 apply 了。
为了提及来省事,虽然 js 没有严格意义的类,我仍是以父类和子类来作区分继承关系。javascript

1. prototype模式继承

既然子类想要继承父类的所有方法,并且咱们知道父类的实例拥有父类全部的方法,那么接下类就好办了,我将子类的 prototype 指向父类的实例,子类就拥有了父类的所有方法了html

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (sex) {
    this.sex = sex;
}
// 实现继承
var p = new Parent('leizore', 25);
Child.prototype = p;
var child = new Child('男');
child.sayName();            // leizore

那么对应的关系图以下:
chain1.png前端

这种方式 Child 继承了 Person 的所有方法,可是也是有缺点的。java

  1. 建立子类实例时,没法向父类构造函数传参。指定 prototype 时,实例化 Person 传的参数,会出如今全部子类上,不灵活。
  2. 由图能够看到,p 的 contructor 指向 Person, 因此 Child.prototype.constructor 也指向 Person,显然会致使继承链的紊乱。

2.借用构造函数继承

针对上面的继承方法的缺点1,开发人员使用一种叫作借用构造函数的技术,也就是咱们平时说的 call 跟 apply 继承。es6

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
     // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}

这里简单讲一下 call(apply)是如何实现的,其实就是将 call(apply) 前面的函数当即执行一遍,而且执行时将做用域 this 指向 call(apply) 函数的第一个参数,好比这里的 call 就是将 Parent 实例一遍,将 name 跟 age 当成参数传过去
这种继承方式解决了继承过程当中的传参问题,可是缺点是并无继承到父类的原型,为了解决这个问题,咱们很容易想到将上面两个方法结合起来不久好了。因而另外一种继承方式出现了面试

3.组合继承

没错,就是两种方式并用,从而发挥二者之长的一种继承模式,代码以下编程

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
Child.prototype = new Parent('leizore', 25);

嗯,这种方式基本上解决了开发过程当中继承的痛点,成为好多人经常使用的继承模式之一。可是缺点也是有的segmentfault

  1. 重复定义了属性,能够看到将 Child 的 prototype指向 Perent 的实例时,继承了name 跟 age 属性,实例 Child 的时候,调用 call 函数,又继承了一次,虽然使用 call 调用此次的属性是在实例属性上,当获取name时优先返回实例属性,而后在 prototype 上,因此并不会出大问题。
  2. 第一种继承方式方式的缺点二也完美的继承过来了,Child.prototype.constructor 仍是指向 parent

那么确定有人会说,既然Child.prototype.constructor 不指向本身,那么直接让他指向本身不就行了?数组

Child.prototype.constructor = Child;

答案是不行的。由于 Child.prototype 是 Parent 的实例,这样操做会将 Parent.prototype.constructor 也指向 Child,显然也是不合理的。app

4.原型式继承

为了解决上面 Child 与 Parent 继承以后纠缠不清的问题,道格拉斯在2006年提出一种继承方法,它的想法是借助原型能够给予已有的对象建立新对象,同时还没必要所以建立自定义类型。函数以下

function object (o) {
    function F() {}
    F.prototype = o;
    return new F();
}

这个模式至关与建立一个新的对象,对象继承了o全部属性,固然这里也只是实现了浅拷贝。

5.组合寄生式继承

嗯,想必你们也想到了,上面这种继承方式能够解决 Child 与 Parent 继承后的纠缠不清的关系。能够由 object 方法建立一个临时对象,从而斩断跟 Parent 的联系。就能够放心的对 Child 原型的constructor 随便指了,固然了为了继承链的不紊乱,仍是指向本身比较好

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
function object (o) {
    function F() {}
    F.prototype = o;
    return new F();
}
var prototype = object(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;


var c = new Child('leizore', 11, 'men');
c.sayName()                // leizore
c.constructor === Child    // true

到此,基本上解决了上面所说的全部缺点。固然了,也是有一点问题的,就是方法四的实现实际上是浅拷贝,若是 Parent.prototype 里又引用类型好比数组,对象,改变Parent.prototype,Child 也会跟着变,解决方式也很简单,使用深拷贝就好了,同时又能够写不少继承方式。固然了,按照我上面顺下来的思想,也能够写出本身的继承方式
好比下面改变object函数:

// 定义父类
function Parent (name, age) {
    this.name = name;
    this.age = age;
}
Parent.prototype.sayName = function () {
    alert(this.name);
}
// 定义子类
function Child (name, age, sex) {
    // 继承,同时传递了参数
    Parent.call(this, name, age)
    this.sex = sex;
}
function object (o) {
    var c = {};
   for (var i in o) {
     c[i] = o[i];
   }
   return c
}
var prototype = object(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;


var c = new Child('leizore', 11, 'men');
c.sayName()                // leizore
c.constructor === Child    // true

固然了,es6 中,能够经过extends关键字实现继承,这里就很少说了

参考

  1. javascript 高级程序设计
  2. Javascript面向对象编程(二):构造函数的继承
相关文章
相关标签/搜索