关于JavaScript继承的几种方法

前言

继承是JavaScript面向对象编程非常重要的一个特性,基本在日常使用以及面试过程中都会使用到。查阅一些资料后整理以下几种继承的方法记录在自己的博客中。

如何实现继承

原型链继承

原型链继承是最简单的一种继承方式,只需要将子类的prototype值等于父类的实例即可

  function Animal(){
    this.superType = 'Animal'
  }
  Animal.prototype.getSuperType = function(){
      console.log(this.superType);
  }
  function Cat(name){
    this.name = name;
    this.type = 'Cat';
  }
  // 原型继承
  Cat.prototype = new Animal();

  var cat = new Cat()

  cat.getSuperType(); // 控制台输出Animal

以上代码是将Animal的实例覆盖Cat的原型,本质是重写原型对象,代之一个新类型的实例。使Cat拥有Animal实例的所有属性和方法(getSuperType为原型方法),并且还有个指针指向了Animal的原型。当创建Cat的实例cat时,cat指向的是Cat的原型,Cat的原型又指向Animal的原型。

缺点

  • 引用类型值的原型属性会被共享
  • 在创建子类型的实例时,无法向超类型的构造函数传递参数

构造函数继承

借用构造函数继承也是非常简单地一种继承方式,即在子类构造函数的内部调用父类型构造函数。代码实现为:

function Animal(name){
  this.name = name;
}
Animal.prototype.getName = function(){
  console.log(this.name);
}
function Cat(){
  Animal.call(this, "cat");
  this.eat = "fish"
}

var instance = new Cat();
console.log(instance.name);
console.log(instance.eat); // 控制台相继输出 cat fish
instance.getName() // error getName undefined

以上代码中的 Animal 只接受一个参数 name ,该参数会直接赋给一个属性。在 Cat 构造函数内部调用 Animal 构造函数时,实际上视为 Cat 的实例设置了 name 属性。为了确保 Animal 构造函数不会重写子类的属性,可以在调用父类构造函数后,再添加应该在子类型中定义的属性。

优点

  • 引用类型的原型属性不会被共享
  • 可以在子类型构造函数中向父类构造函数传递参数

缺点

  • 方法都在构造函数中定义,函数无法复用
  • 在父类的原型定义的方法,对子类型是不可见,导致所有的类型都只能使用构造函数

组合继承

组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数组合到一块

function SuperType(name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}
SuperType.prototype.getName = function() {
  console.log(this.name)
}
function SubType(name, age) {
  // 继承属性
  SuperType.call(this, name)
  this.age = age
}
// 继承方法
SubType.prototype = new SuperType()

SubType.prototype.sayAge = function () {
  console.log(this.age)
}
let instance1 = new SubType("tom", "8")
instance1.colors.push("black")
console.log(instance1.colors) // red, blue, green, black
instance1.getName()  // tom
instance1.sayAge()  // 8

let instance2 = new SubType("jerry", "9")
console.log(instance2.colors) // red, blue, green
instance2.getName() // jerry
instance2.sayAge()  // 9

此例子中, SuperType 构造函数定义了两个属性: name colors SuperType 定义了一个方法 sayName() SubType 构造函数在调用 SuperType 构造函数时传入 name 参数,紧接着又定义了它自己的属性 age 。然后将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge 。这样就可以让两个不同的 SubType 实例即拥有自己的属性,又可以使用相同的方法。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceofisPortotypeOf() 也能够用于识别组合继承创建的对象。

缺点

  • 会调用两次超类构造函数,并且会分别在实例和原型上有重复的属性

原型式继承

原型式继承:其思想是借助原型,可以基于已有的对象创建新的对象,同时还不用创建自定义类。以下代码,在 object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数原型,最后返回了这个临时类型的一个新实例。从本质上讲, object() 对传入其中的对象执行了一次浅复制。

function object(o) {
  function F(){}
  F.prototype = o
  return new F()
}
var person = {
  name: 'Tom',
  friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

var yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob

还可以使用 ES5 新增的 Object.create() 方法进行创建

var anotherPerson = Object.create(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

var yetAnotherPerson = Object.create(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob

如果只是想让一个对象与另一个对象保持类似的情况下,原型式继承是可以完全胜任的

寄生式继承

寄生式继承的思路与构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象

function createAnother(original) {
  var clone = Object.create(original)
  clone.sayHi = function () {
    console.log('Hi')
  }
  return clone
}

上述代码中, createAnother() 函数接收一个参数,也就是将要作为新对象基础的对象。然后,把 original 传递给 object 函数,将返回的结果赋值给clone。再为 clone 对象添加一个新方法 sayHi() ,最后返回 clone 对象。

var person = {
  name: 'Tom',
  friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() // Hi

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

寄生组合式继承

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为指定子类行的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。代码如下:

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}
function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType (name, age) {
  SuperType.call(this, name)
  this.age = age
}

inheritPrototype(subType, SuperType)

SubType.prototype.sayAge = function () {
  console.log(this.age)
}

该代码只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上创建不必要的,多余的属性。与此同时,原型链还能保持不变。属于最理想的继承范式。

总结

继承是 JS 中极为重要的一块知识,目前只能参考资料将这些记录下来,完全吃透还需要慢慢实战。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值