继承
原型链继承
function Parent () {
this.name = 'Jack'
}
Parent.prototype.getName = function () {
return this.name
}
function Child () {
this.age = 20
}
Child.prototype = new Parent()
Child.prototype.getAge = function () {
return this.age
}
var person = new Child()
console.log(person.getName()) // Jack
分析:看这句代码 Child.prototype = new Parent()
,Child 的原型被换成了 Parent 的实例,目前 Child 的新原型不仅具有作为一个 Parent 的实例所拥有的全部属性和方法,而且内部还有一个指针,指向了 Parent 的原型。 调用 person.getName()
这个会经历三个步骤:1、搜索实例;2、搜索 Child.prototype;3、搜索 Parent.prototype,最后一步才找到结果。通过实现原型链,Child 的实例成功在 Parent 定义中找到了需要的方法。
问题:1、引用类型值的原型属性会被所有实例共享;2、在创建子类型的实例时,不能向父类型构造函数传递参数;
第一个问题举例如下:
function Parent () {
this.fruit = ['apple', 'pear']
}
function Child () {}
Child.prototype = new Parent()
var one = new Child()
one.fruit.push('banner')
console.log(one.fruit) // apple, pear, banner
var two = new Child()
console.log(two.fruit) // apple, pear, banner
因此实践中很少单独使用原型链。
借用构造函数
function Parent (name) {
this.fruit = ['apple', 'pear']
this.name = name
}
function Child (name) {
// 继承 Parent,同时还传递了参数
Parent.call(this, name)
// 实例属性
this.age = 20
}
var person0 = new Child('Jack')
person0.fruit.push('banner')
console.log(person0.fruit) // ["apple", "pear", "banner"]
console.log(person0.name) // Jack
var person1 = new Child()
console.log(person1.fruit) // ["apple", "pear"]
优点:
- 子类型构造函数可以向父类型构造函数传递参数
- 引用类型值不会被实例共享
缺点:
- 每个方法都要在每个实例上重新创建一遍
- 方法都在构造函数中定义,因此函数复用无从谈起
因此借用构造函数也是很少单独使用。
组合继承
function Parent (name) {
this.fruit = ['apple', 'pear']
this.name = name
}
Parent.prototype.sayName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
// 继承方法
Child.prototype = new Parent()
Child.prototype.constructor = Child // 修复子类实例 constructor 指向,不然会指向 Parent
Child.prototype.sayAge = function () {
console.log(this.age)
}
var person1 = new Child('Jack', 20)
person1.fruit.push('banner')
console.log(person1.fruit) // ["apple", "pear", "banner"]
person1.sayName() // Jack
person1.sayAge() // 20
var person2 = new Child('Mike', 18)
person2.fruit.push('watermelon')
console.log(person2.fruit) // ["apple", "pear", "watermelon"]
person2.sayName() // Mike
person2.sayAge() // 18
组合继承避免了原型链和借用构造函数的缺陷,结合了它们的优点,成为 JavaScript 中最常用的继承模式。
原型式继承
function objectCreate (o) {
function F () {}
F.prototype = o
return new F()
}
var obj = {
name: 'color',
colors: ['black', 'blue']
}
var obj1 = objectCreate(obj)
obj1.name = 'fruit'
obj1.colors.push('red')
var obj2 = objectCreate(obj)
obj2.name = 'vehicle'
obj2.colors.push('yellow')
console.log(obj.name) // color
console.log(obj.colors) // ['black', 'blue', 'red', 'yellow']
console.log(obj1.name) // fruit
console.log(obj2.name) // vehicle
当 ES5 的 Object.create()
只传入第一个参数时,上面的 objectCreate()
方法就是它的实现原理。
缺点是包含引用类型值的属性始终都会共享相应的值。
寄生式继承
这种继承模式和原型式继承紧密联系。即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
// 原型式继承
function objectCreate (o) {
function F () {}
F.prototype = o
return new F()
}
// 寄生式继承
function obj (orgObj) {
var clone = objectCreate(orgObj)
// var clone = Object.create(orgObj) ES5 api
clone.sayHi = function () {
console.log('Hi')
}
return clone
}
缺点:每次创建对象都要创建一遍方法,降低函数的复用效率。
寄生组合式继承
// 关键函数
function inheritPrototype (child, parent) {
var prototype = objectCreate(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
function objectCreate (o) {
function F () {}
F.prototype = o
return new F()
}
function Parent (name) {
this.name = name
this.colors = ['black', 'blue']
}
Parent.prototype.sayName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = function () {
console.log(this.age)
}
var demo = new Child('Mike', 22)
demo.sayName() // Mike
demo.sayAge() // 22
这个例子的高效率提现在它只调用了一次 Parent 构造函数,并且因此避免了在 Child.prototype 上面创建不必要的,多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof
和 isPrototypeOf()
。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
结语
这篇总结主要参考《JavaScript高级程序设计》中的继承章节。