继承

7,955 阅读3分钟

1、原型链继承

原理

子类原型对象为父类实例

demo

image.png

c.info访问顺序:Child构造函数无info,去Child的原型对象Child.prototype找,其值等于Parent实例。该实例有info,则返回。若访问c.getName那么到了Child.prototype即Parent实例没有的情况下,会继续往上,到new Parent().__proto__上找。即Parent.prototype找到

优点

写法简单,直接子类原型对象为父类实例

缺点

父构造函数有引用类型属性,所有Child实例则会互相影响

父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。其中一个子类实例进行修改,会导致所有其他子类实例的这个值都会改变

构造函数继承

原理

在子构造函数内执行父构造函数,相当于把父构造函数方法体,即this.** = * 同样在子构造函数中粘贴过来了,这样子构造函数方法体就固定了。

后续多个实例之间即便是引用类型属性也互不影响,因为属性来自于构造函数,而不是原型链上的公共属性。

demo

image.png

c和c2互不影响,name不是原型链上的公共属性

优点

引用属性互不影响:解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参)

缺点

所有方法都定义在构造函数中,每次都需要重新创建(对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法)

image.png

组合继承

原理

通过构造函数实现属性继承,通过原型实现公共方法继承

demo

image.png

非常重要的一步

image.png

优点

既解决了构造函数引用类型属性互相影响的问题,又解决了方法被实例重复创建的问题

缺点

父类构造函数被调用了两次。同时子类实例以及子类原型对象上都会存在name属性。虽然根据原型链机制,并不会访问到原型对象上的同名属性,但总归是不美。

确实,Parent调用了两次,Child和Parent都有name属性

寄生组合继承

原理

寄生组合继承其实就是在组合继承的基础上,解决了父类构造函数调用两次的问题。我们来看下如何解决的:

demo

function Parent() {
  this.name = 'fedaily'
}

Parent.prototype.getName = function() {
  return this.name
}

function Child() {
  Parent.call(this)
  this.topic = 'fe'
}

// 仔细看这个函数的实现
inherit(Child, Parent)
function inherit(child, parent) {
  var prototype = object(parent.prototype)
  prototype.constructor = child
  child.prototype = prototype
}

// 这个函数的作用可以理解为复制了一份父类的原型对象
// 如果直接将子类的原型对象赋值为父类原型对象
// 那么修改子类原型对象其实就相当于修改了父类的原型对象
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

优点

这种方式就解决了组合继承中的构造函数调用两次,构造函数引用类型共享,以及原型对象上存在多余属性的问题。是推荐的最合理实现方式(排除ES6的class extends继承哈哈哈)。

缺点

没有啥特别的缺点

ES6

原理

ES6提供了class语法糖,同时提供了extends用于实现类的继承。这也是项目开发中推荐使用的方式。

完全基于组合继承,只不过通过class语法糖写法简单

使用class继承很简单,也很直观:

demo

image.png