几种JS里常见的继承(原型链继承、构造函数继承和组合继承)

JS里的继承有很多种,此篇文章只举了三种普通常见的继承类型,希望能为读者日常的学习提供一定的帮助。

原型链继承

我们通过一个例子来了解什么是原型链继承

function SuperType() { // 超类
  this.superproerty = true
}
SuperType.prototype.getSuperValue = function () {
  return this.superproerty
}
function SubType() { // 子类
  this.subproperty = false
}
SubType.prototype.getSuperValue = function () {
  return this.subproperty
}
SubType.prototype = new SuperType()
var instance = new SubType()
console.log(instance.getSuperValue()); 
复制代码

问:最后输出是什么?true 还是 false ?
答案: 输出 true
原因:SubType.prototype = new SuperType()这一行代码用 SuperType 类型的一个实例重写了 SubType 的原型对象,使得SubType 的原型对象找到了 SuperType 的原型对象。

这个例子中, SubType 继承了 SuperTypeSuperType 我们称之为超类,继承的方式就是通过new SuperType()创建 SuperType 实例,且将该实例赋值给 SubType 的原型来实现的。而实现的本质,是重写子类型的原型对象。

原型链:instance -> SubType的原型 -> SuperType的原型 -> Object的原型

这就是原型链继承,让子类的原型等于超类的实例,实现原型链继承。
缺点:

  1. 超类构造函数中定义的引用类型的实例属性,会在子类的原型上变成属性,被所有子类的实例所共享。
  2. 在创建子类型的实例时,不能向超类的构造函数中传递参数。

构造函数继承

构造函数可以很好的解决原型链继承中的缺点
看一个例子

function SuperType() {
  this.colors = ['red', 'green', 'blue']
}
function SubType() {
  SuperType.call(this)
}
var instance = new SubType()
instance.colors.push('pink')
console.log(instance.colors); // [ 'red', 'green', 'blue', 'pink' ]
复制代码

首先解释一下 call( ) 的用处:

利用call()能够使用属于另一个对象的方法。

我们再看上面的例子,SuperType.call(this)就是将 SuperType 自己的作用域搬到 SubType 里面去了,等同于在 SubType 里面复制了一份 SuperType 的作用域,并不会影响 SuperType 自己的作用域。

function SuperType(color) {
  this.colors = ['red', 'green', 'blue']
  this.color = color
}
function SubType(color) {
  SuperType.call(this, color)
}
var instance = new SubType()
var instance4 = new SubType('yellow')
var instance2 = new SuperType()  // 作用域没有被影响
var instance3 = new SubType()
instance.colors.push('pink')
console.log(instance.colors); // [ 'red', 'green', 'blue', 'pink' ]
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
console.log(instance2.color); // undefined 实例instance4并未影响超类作用域
console.log(instance3.colors); // [ 'red', 'green', 'blue' ]
console.log(instance4.color); // yellow
复制代码

可以看到这四个实例中,作用域并不是同一个,且传参不会混淆。这就很好的解决了原型链的两个缺点。
但它也有缺点
缺点:

  1. 子类的实例对象上可能会存在非必要的属性。
  2. 无法继承到超类原型上的属性。

组合继承

原型链继承 + 构造函数继承
直接上例子

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'green', 'blue']
}
SuperType.prototype.sayName = function () {
  console.log(this.name);
}

function SubType(name, age) {
  SuperType.call(this, name)  // 构造函数继承
  this.age = age
}
SubType.prototype = new SuperType()  // 原型链继承,用SuperType类型的一个实例来重写SubType类型的原型对象
SubType.prototype.sayAge = function () {
  console.log(this.age);
}

var instance1 = new SubType('老鱼', 18)
instance1.colors.push('pink')
console.log(instance1.colors); // ['red', 'green', 'blue', 'pink']
instance1.sayName() // 老鱼
instance1.sayAge() // 18

var instance2 = new SubType('虾米', 20)
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
instance2.sayName() // 虾米
instance2.sayAge() // 20
复制代码

上述代码将原型链继承和构造函数继承的优点都利用了,使其规避了两种继承的缺点。

注意:SubType.prototype = new SuperType()一定要在SubType.prototype.sayAge = function () {console.log(this.age);}这行代码之前,如果次序反了会导致sayAge()被覆盖,从而导致sayAge()无法调用。

缺点:

  1. 无论在什么情况下,都会调用两次超类构造函数。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享