本文将从最简单的例子开始,从零讲解在 JavaScript 中如何实现继承。bash
如今有个需求,须要实现 Cat 继承 Animal ,构造函数以下:函数
function Animal(name){
this.name = name
}
function Cat(name){
this.name = name
}
复制代码
注:如对继承相关的 prototype、constructor、__proto__、new 等内容不太熟悉,能够先查看这篇文章:理性分析 JavaScript 中的原型post
在实现这个需求以前,咱们先谈谈继承的意义。继承本质上为了提升代码的复用性。性能
对于 JavaScript 来讲,继承有两个要点:ui
下面的内容将围绕这两个要点展开。this
复用父构造函数中的代码,咱们能够考虑调用父构造函数并将 this 绑定到子构造函数。spa
复用父原型中的代码,咱们只需改变原型链便可。将子构造函数的原型对象的 __proto__ 属性指向父构造函数的原型对象。prototype
初版代码以下:设计
function Animal(name){
this.name = name
}
function Cat(name){
Animal.call(this,name)
}
Cat.prototype.__proto__ = Animal.prototype
复制代码
检验一下是否继承成功:咱们在 Animal 的原型对象上添加 eat 函数。使用 Cat 构造函数生成一个名为 'Tom' 的实例对象 cat 。代码以下:code
function Animal(name){
this.name = name
}
function Cat(name){
Animal.call(this,name)
}
Cat.prototype.__proto__ = Animal.prototype
// 添加 eat 函数
Animal.prototype.eat = function(){
console.log('eat')
}
var cat = new Cat('Tom')
// 查看 name 属性是否成功挂载到 cat 对象上
console.log(cat.name) // Tom
// 查看是否能访问到 eat 函数
cat.eat() // eat
// 查看 Animal.prototype 是否位于原型链上
console.log(cat instanceof Animal) // true
// 查看 Cat.prototype 是否位于原型链上
console.log(cat instanceof Cat) //true
复制代码
经检验,成功复用父构造函数中的代码,并复用父原型对象中的代码,原型链正常。
__proto__ 属性虽然能够很方便地改变原型链,可是 __proto__ 直到 ES6 才添加到规范中,存在兼容性问题,而且直接使用 __proto__ 来改变原型链很是消耗性能。因此 __proto__ 属性来实现继承并不可取。
针对 __proto__ 属性的弊端,咱们考虑使用 new 操做符来替代直接使用 __proto__ 属性来改变原型链。
咱们知道实例对象中的 __proto__ 属性指向构造函数的 prototype 属性的。这样咱们 Animal 的实例对象赋值给 Cat.prototype 。不就也实现了Cat.prototype.__proto__ = Animal.prototype
语句的功能了吗?
代码以下:
function Animal(name){
this.name = name
}
function Cat(name){
Animal.call(this,name)
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
复制代码
使用这套方案有个问题,就是在将实例对象赋值给 Cat.prototype 的时候,将 Cat.prototype 原有的 constructor 属性覆盖了。实例对象的 constructor 属性向上查询获得的是构造函数 Animal 。因此咱们须要矫正一下 Cat.prototype 的 constructor 属性,将其设置为构造函数 Cat 。
兼容性比较好,而且实现较为简单。
使用 new 操做符带来的弊端是,执行 new 操做符的时候,会执行一次构造函数将构造函数中的属性绑定到这个实例对象。这样就多执行了一次构造函数,将本来属于 Animal 实例对象的属性混到 prototype 中了。
考虑到第二版的弊端,咱们使用一个空构造函数来做为中介函数,这样就不会将构造函数中的属性混到 prototype 中,而且减小了多执行一次构造函数带来的性能损耗。
代码以下:
function Animal(name){
this.name = name
}
function Cat(name){
Animal.call(this,name)
}
function Func(){}
Func.prototype = Animal.prototype
Cat.prototype = new Func()
Cat.prototype.constructor = Cat
复制代码
使用 ES6 就方便多了。可使用 extends 关键字实现继承, 复用父原型中的代码。使用 super 关键字来复用父构造函数中的代码。
代码以下:
class Animal {
constructor(name){
this.name = name
}
eat(){
console.log('eat')
}
}
class Cat extends Animal{
constructor(name){
super(name)
}
}
let cat = new Cat('Tom')
console.log(cat.name) // Tom
cat.eat() // eat
复制代码