基于原型链的继承
基于原型链的继承的问题,原型上的引用属性会被多个实例所共享,修改一个实例的该属性会导致其他实例的该属性一起被修改.
function Super(){
this.name = 'super'
this.colors = ['red','green','blue']
}
function Sub(){
}
Sub.prototype = new Super()
let sub1 = new Sub()
let sub2 = new Sub()
console.log(sub1.name)//super
sub1.colors.push('black')
console.log(sub1.colors,sub2.colors)//[ 'red', 'green', 'blue', 'black' ] [ 'red', 'green', 'blue', 'black' ]
借用构造函数进行继承
原理:在子类的构造函数中调用父类构造函数,使用 apply()和 call()方法以新创建的对象为上下文执行构造函数.
优点:可以传递参数
function Super(name){
this.name = name
this.colors = ['red','green','blue']
}
function Sub(name){
Super.call(this,name)
}
let sub1 = new Sub('sub')
console.log(sub1.name)//sub
问题:必须在构造函数中定义方法,因此函数不能重用.此外,子实例也不能访问父实例原型上定义的方法.
组合继承
组合继承(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来.
function Super(name){
this.name = name
this.sayMyName = function(){
console.log(this.name)
}
}
//继承了属性
function Sub(name){
Super.call(this,name)
}
//继承了方法
Sub.prototype = new Super()
let sub1 = new Sub('sub')
console.log(sub1.name)//sub
sub1.sayMyName()//sub
组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript中使用最多的继承模式.而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力. 但是也存在缺点,父级的构造函数会被调用两次,增加了内存开销.
原型式继承
适用情况:你有一个对象,想在它的基础上再创建一个新对象,新的对象会以已有对象为原型.
let originO = {
name:'lhc',
age:20,
cars:['benz']
}
function object(o){
function F(){}
F.prototype = o
return new F()
}
let sub1 = object(originO)
sub1.name = 'james'
sub1.age = 34;
sub1.cars.push('bmw')
let sub2 = object(originO)
sub2.name = 'wade'
sub2.age = 37
sub2.cars.push('linken')
console.log(sub1,sub2,originO.cars)//{ name: 'james', age: 34 } { name: 'wade', age: 37 } [ 'benz', 'bmw', 'linken' ]
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合.但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
ECMAScript 5 通过增加 Object.create()方法将原型式继承的概念规范化了.
Object.create()的第二个参数与 Object.defineProperties()的第二个参数一样:每个新增属性都通过各自的描述符来描述.以这种方式添加的属性会遮蔽原型对象上的同名属性。
let originO = {
name:'lhc',
age:20,
cars:['benz']
}
let newO = Object.create(originO,{
name:{
value:'wade'
}
})
console.log(newO.name)//wade
寄生式继承
寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象.
function object(o){
function F(){}
F.prototype = o
return new F()
}
//工厂函数对对象进行增强,最后返回这个对象
function createAnotherObject(obj){
let clone = object(obj)
clone.say = function(){
console.log('hi')
}
return clone
}
let person = {
name:'lhc',
age:20
}
let clone = createAnotherObject(person)
clone.say()//hi
这个例子基于 person 对象返回了一个新对象.新返回的 anotherPerson对象具有 person 的所有属性和方法,还有一个新方法叫 sayHi().寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
寄生式组合继承
组合继承其实也存在效率问题.最主要的效率问题就是父类构造函数始终会被调用两次:一次在是创建子类原型时调用,另一次是在子类构造函数中调用。本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法.基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本.
该继承方式是目前不使用ES6类的情况下的最佳继承方式,避免了父级构造函数调用两次的问题,可以实现原型上函数的复用,并且可以传递参数.
function object(o){
function F(){}
F.prototype = o
return new F()
}
function inheritPrototype(subType,superType){
let prototype = object(superType.prototype)//创建父对象原型的副本
prototype.constructor = subType//对该父对象原型的副本进行增强,避免重写原型后导致的constructor丢失
subType.prototype = prototype//赋值对象
}
function SuperType(name){
this.name = name
}
SuperType.prototype.say = function(){
console.log(this.name)
}
function Subtype(age,name){
this.age = age
SuperType.call(this,name)
}
inheritPrototype(Subtype,SuperType)
Subtype.prototype.sayAge = function(){
console.log(this.age)
}
let sub = new Subtype(20,'lhc')
sub.say()//lhc
sub.sayAge()//20
console.log(sub.__proto__.constructor === Subtype)//true