继承是面向对象语言(Object-Oriented Language)中的一个最为人津津乐道的概念。JS中的继承主要是依靠原型链 来实现的。
原型链
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原
型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每
个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型
对象的内部指针(__proto__)。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的
原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数
的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实
例与原型的链条。这就是所谓原型链的基本概念。
ES5实现
结合前面的构造函数和原型的概念,我们先定义一个构造函数
function Animal(name){
this.name = name
}
Animal.prototype.run = function(){
console.log(this.name + ' is running')
}
再定义子类的构造函数:
function Dog(color, name){
Animal.call(this, name)
this.color = color
}
Dog.prototype.jump = function(){
console.log(this.name + ' ' + this.color + ' is jumping')
}
这里需要注意的是,Animal.call(this, name)通过使用apply()/call()方法也可以在(将来)新创建的对象上执行构造函数。
然后我们需要将Dog.prototype的__proto__指向Animal.prototype,实现原型链的继承
var tmp = function(){}
tmp.prototype = Animal.prototype
Dog.prototype = new tmp()
这里使用一个函数作为“桥梁”,让它的原型对象指向Animal的原型对象,再用new操作符,将Dog
的原型对象的__proto__指向它的原型对象。为什么不直接new Animal呢?因为直接用的话要注意传入name形成实例,这里相当于只是复制了一份Animal.prototype。
事实上这三部可以用Dog.prototype = Object.create(Animal.prototype)来代替,本质上是一样的。MDN Object.create
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constuctor = Dog
这里需要将Dog的原型对象的constructor指向其构造函数,也就是Dog。这样就完整的实现了一个JS继承。
最后是使用:
var ex = new Dog('red','来福')
console.dir(Animal)
console.dir(Dog)
console.log('ex: ')
console.dir(ex)
ES6实现
ES6实际上是JavaScript现有的基于原型的继承的语法糖。详细可以参考MDN extend
class person {
constructor(name, age){
this.name = name
this.age = age
}
say(){
console.log(`${this.name} saying`)
}
}
const a = new person('kuma', 29)
a.say() //"kuma saying"
class man extends person {
constructor(name, age, sex){
super(name, age)
this.sex = sex
}
run() {
console.log(`${this.name} running ${this.sex}`)
}
}
const b = new man('alex', 29, 'is man')
b.run() //"alex running is man"
b.say() //"alex saying"
相比较而言,ES6的写法更容易理解和使用,项目中如果为了兼容,使用babel转译即可。