wangzhenggui/blog

javaScript经典系列之几种继承的实现方式

Opened this issue · 0 comments

JS中常用的几种继承方式

一、原型链继承

function SuperType() {
    this.property = true;
    this.numbers = [1,2,3,4]
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType(); 

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true

上面有俩个构造函数SubType和SuperType,我们通过将SubType的实例原型对象指向了SuperType的实例对象,从而达到能够获得到SuperType中的属性

image
⚠️注意:上图中左端的构造函数SuperType为SubType
这种继承方式的弊端

var instance1 = new SubType();
instance1.numbers.push(5)
var instance2 = new SubType();
console.log(instance1.numbers) // [1,2,3,4,5]
console.log(instance2.numbers) // [1,2,3,4,5]

上面的代码我们希望的结果是instance1的numbers数组添加一个数字5,但是我们可以看到这个时候instace2这个对象的numbers属性的值也发生了变化,所以我们这种继承方式还不够完美。

优点

  • 父类新增原型方法/原型属性,子类都能访问到
  • 简单,易于实现

缺点

  • 无法实现多继承
  • 来自原型对象的所有属性被所有实例共享
  • 创建子类实例时,无法向父类构造函数传参
  • 要想为子类新增属性和方法,必须要在SubType.prototype = new SuperType(); 之后执行,不能放到构造器中

二、借用父类构造函数实现继承

function SuperType() {
  this.numbers = [1,2,3,4]
}
SuperType.prototype.getNumbers = function() {
  console.log(this.numbers)
}
function SubType() {
  SuperType.call(this) // 子类的构造函数中调用父类的构造函数
}

上面代码的核心代码就是在子类的构造函数中调用了父类的构造方法,将父类的属性都copy一份到当前的对象上来了。我们现在看看会不会出现上面那种俩个对象公用同一个内存的事情!

var instance1 = new SubType();
instance1.numbers.push(5)
var instance2 = new SubType();
console.log(instance1.numbers) // [1,2,3,4,5]
console.log(instance2.numbers) // [1,2,3,4]
instance1.getNumbers() // instance1.getNumbers is not a function

我们发现现在不会出现一个对象修改属性会对另外一个对象产生影响了,但是我们发现这里又出现了一个新的问题,就是子类的实例对象访问不了父类原型链上的方法和属性。
优点

  • 解决了原型链继承中子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call多个父类对象)
    缺点
  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承原型属性和方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

三、原型链+借用父类构造函数方式继承

这种方式就是将上面俩种方式的优点给结合一下,通过原型链继承方式可以访问到原型对象上的属性和方法,通过调用父类构造函数的方式避免多个对象共同操作一个属性的行为。

function SuperType(name,age) {
  this.name = name
  this.age = age
}
SuperType.prototype.getName = function() {
  console.log(this.name)
}
function SubType(name,age) {
  SuperType.call(this.name,age)
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.getAge = function () {
  console.log(this.age)
}

var sub1 = new SubType('sub1',11)
sub1.getName() // sub1
sub1.getAge() // 11
var sub2 = new SubType('sub2',12)
sub2.getName() // sub2
sub2.getAge() // 12

这种方式融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。不过也存在缺点就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性

优点

  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺点

  • 调用了两次父类构造函数,生成了两份实例

四、组合继承方式优化方案

借助Object.create这个API来创建一个以父类实例的原型对象为基础,生成一个继承了这个原型对象所有属性的对象

function SuperType(name,age) {
  this.name = name
  this.age = age
}
SuperType.prototype.getName = function() {
  console.log(this.name)
}
function SubType(name,age) {
  SuperType.call(this,name,age)
}

SubType.prototype = Object.create(SuperType.prototype)  // 这里是核心
SubType.prototype.constructor = SubType
SubType.prototype.getAge = function () {
  console.log(this.age)
}

var sub1 = new SubType('sub1',11)
sub1.getName() // sub1
sub1.getAge() // 11
var sub2 = new SubType('sub2',12)
sub2.getName() // sub2
sub2.getAge() // 12

这种方式现在是最好的继承方式!

五、 ES6中的Class与extends实现继承

可以看下React中的代码,其实这里的Class只是一个语法糖,底层实现还是通过原型链的方式实现的!