在看《javascript精粹》这本书时,继承这个章节看得有些迷糊,和《JavaScript高级程序设计》中的思路差异比较大,于是回头看了一下《JavaScript高级程序设计》,安慰一下受伤的智商,总结一下,《javascript精粹》真的需要反复看很多遍。之后看明白了再来比较一下两本书内容的差别。
1. 原型链继承
原型链继承的本质就是将一个类型的实例赋给另一个类型的原型实现的
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property
}
function SubType(){
this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function(){
return this.subproperty
}
var instance = new SubType()
在这里,我们给SubType换了一个全新的原型,里边包括SuperType的全部属性和方法,内部还有一个指针,指向了SuperType的原型。instance作为subType的实例,对于它其中的属性和方法的调用就和原型的搜索机制一样了,依次向上搜索,如果调用了getSuperValue方法,首先会搜索实例,其次是SubType,最后是SuperType。此外,任何的函数都有一个指向Object原型的指针,这个就不在刚才的搜索链里边了,属于另一个继承层次。原型链继承存在的问题包含引用类型值的原型属性会被所有实例共享。因为原型省略了构造函数初始化传递参数这一环节,所有的实例在默认情况下都取得了相同的属性值,这里其实也是原型最大问题的体现,是有原型共享的本性导致的。由于SubType继承了SuperType,所以colors属性存在于SubType的prototype中,所以对于SubType的子孙来说,colors只有父类原型当中的一个。
function SuperType(){
this.colors = [red,blue]
}
function SubType(){}
SubType.prototype = new SuperType()
var instance1 = new SubType()
instance1.colors.push('yellow')
var instance2 = new SubType()
console.log(instance1.colors)//red blue yellow
console.log(instance2.colors)//red blue yellow
可以看到,这里只在instance1中push了一个值,instance2实例中并没有对colors进行操作,但两个实例的属性值是一样的,这就是使用原型链继承存在的第一个问题。另外一个,就是不能在不影响其他实例的情况下向超类中传递参数。鉴于以上两个问题,实际时单独应用原型链的时候很少。
“包含引用类型值的原型属性会被所有实例共享”,我对这句话的理解是,经过subType对supertType的原型继承,在subType实例使用colors时,colors是存在于subType原型中的,所以它会被共享。书中有些话一开始不理解,经过反复阅读,自己总结下来,对于原型的有了进一步的认识。
2. 借用构造函数
思想:在子类型的构造函数中调用超类型构造函数。
function SuperType(){
this.colors = ['red','blue']
}
function SubType(){
SuperType.call(this)
}
借用构造函数的优点是可以传递参数,比如:
function SuperType(name){
this.name = name
}
function SubType(){
SuperType.call(this,'haha')
}
但是这种方法也是存在问题的,比如超类原型中存在的方法和属性我们就获取不到了,只能获取到构造函数中的属性和方法。
3. 组合继承
思想:将原型链继承和借用构造函数继承结合在一起,即借用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。
function SuperType(name){
this.name = name
this.colors = ['red','blue']
}
SubType.prototype.sayName = function(){alert(this.name)}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function(){alert(this.age)}
var instance1 = new SubType('ha',13)
instance1.colors.push('yellow')
instance1.sayName()//'ha'
console.log(instance1.colors) //'red,blue,yellow'
var instance2 = new SubType('haha',131)
instance2.sayName()//'haha'
console.log(instance1.colors) //'red,blue'
这样其实是有两组name和color属性的,一组在SubType原型中,另一组通过SubType实例化得出,经过实例化得出属性就覆盖掉了SubType原型当中的属性。组合式继承巧妙的解决了原型链继承中引用类型值原型属性和借用构造函树继承中的无法获取超类方法和属性的问题,成为最常用的继承模式。
4. 原型式继承
这种方法并没有上述方法中的构造函数,而是基于一个已有的对象,利用Object的create方法(ES5),将这个已有的对象作为原型,创建新对象的过程。
create方法接受两个参数,第一个是已有对象,第二个是新对象的特有属性。
var person = {
name : 'haha',
friends : ['a','b']
}
var newPerson1 = Object.create(person,{name:{
value : 'heihei'
}})
var newPerson2 = Object.create(person,{name:{
value : 'hehe'
}})
console.log(newPerson1.name) //heihei
console.log(newPerson2.name) //hehe
newPerson1.friends.push('c')
console.log(newPerson1.friends) //['a','b','c']
console.log(newPerson2.friends) //['a','b','c']
可以看到,使用这种方法可以快速的创建具有类似属性的对象,同时又可以针对特有的属性值进行分别设置,同时省去了创建一些构造函数的麻烦,在合适的情况下效率是比较高的,关键词是对象相似。
5. 寄生式继承
思路:构建一个封装了继承过程的函数,传入一个原有对象,在函数内部根据传入的对象创建一个新对象并赋予新对象新的方法。
function createAnother(original){
var clone = object(original) //object一个返回新对象的函数
clone.sayHi = function(){}
return clone
}
和原型式继承一样,这里不需要构造函数和自定义类型,只要传入一个已存在的对象即可。
6. 寄生组合式继承
组合继承中在继承超类构造函数属性和超类原型方法时调用了两次超类构造函数,寄生组合式继承将第二次调用超类构造函数给省去了。具体方法就是创建一个接收子类和超类构造函数的函数,在其中修改子类的prototype,实现对超类方法的继承。
function inheritPrototype(SubType,SuperType){
var prototype = Object(SuperType.prototype)
prototype.constructor = SubType
SubType.prototype = prototype
}
function SuperType(name){
this.name = name
this.colors = ['red','blue']
}
function SubType(name,age){
SuperType.call(this,name)
}
// SubType.prototype = new SuperType()
inheritPrototype(SubType,SuperType)