(一)原型链继承
原理:是利用原型让一个引用类型继承另一个引用类型的属性和方法
function Cat(){
this.category='我是猫科'
}
Cat.prototype.getCategory=function(){
return this.category
}
function Tiger(){
this.name = '大老虎'
}
Tigger.prototype = new Cat()
Tigger.prototype.getName=function(){
return this.name
}
var instace = new Tiger()
// 在实现继承之后,通过Tiger创建的实例,既有Tiger的方法与属性,又有Cat的方法与属性
console.log('我是'+instace.getName()+',我来自'+instace.getCategory())
以上代码定义了两个类型:Cat和 Tiger。每个类型分别有一个属性和一个方法。在这里Tiger继承了 Cat,而继承是通过创建 Cat的实例,并将该实例赋给Tiger.prototype 实现的。实现的本质是重写原型对象,原来存在于 Cat的实例中的所有属性和方法,现在也存在于 Tiger.prototype 中了。
原型链继承存在的问题:(1)所有实例共享原型,那么当其中一个实例改变了共享的属性,那么其余实例上对应的属性也被更改(2)在创建子类型的实例时,不能向超类型的构造函数中传递参数
总结:实践中很少会单独使用原型链
(二)借用构造函数(伪造对象或经典继承)
基本实现思想:在子类型构造函数的内部调用超类型构造函数
function Cat(){
this.colos=['red','blue','yellow']
}
function Tiger(){
//继承了Cat,通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数
Cat.call(this)
}
var instance1 = new Tiger()
instance1.colos.push('wwww')
var instace2 = new Tiger()
console.log(instace2.colos) // ["red", "blue", "yellow"]
相对于原型链而言,借用构造函数可以在子类型构造函数中向超类型构造函数传递参数
function Cat(name){
this.name = name
}
function Tiger(){
// 继承同时传参
Cat.call(this,'ccc')
// 实例属性
this.age = 15
}
var instace2 = new Tiger()
console.log(instace2.name,instace2.age) //ccc 15
借用构造函数存在的问题是:如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的(即父类原型链上的方法与属性在这种方式实现的时候,子类继承不了),结果所有类型都只能使用构造函数模式。
总结:借用构造函数的技术也是很少单独使用的
(三)组合继承
基本实现思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性
function Cat(name){
this.name = name
this.color=['red','yellow','block']
}
Cat.prototype.asyName=function(){
console.log('我是猫科动物'+this.name)
}
function Tiger(name,age){
// 继承属性
Cat.call(this,name) //第二次调用 Cat() ,在新对象上创建了实例属性 name 和 color
this.age = age
}
// 继承方法
Tiger.prototype = new Cat() //第一次调用 Cat() ,Tiger.prototype 会得到两个属性:name 和 color
Tiger.prototype.constructor = Tiger
Tiger.prototype.sayAge=function(){
console.log(this.age)
}
var ins1 = new Tiger('ccc',18)
ins1.color.push('mmm')
console.log(ins1.color)
ins1.asyName()
ins1.sayAge()
console.log('*****************')
var ins2 = new Tiger('ddd',15)
console.log(ins2.color)
ins2.asyName()
总结:JavaScript 中最常用的继承模式
组合继承在实现的时候,初始化了两次父类构造函数,那么可以进行优化,
//优化前部分代码
Tiger.prototype = new Cat()
//优化后部分代码
Tiger.prototype = Cat.prototype
还有一个问题就是通过组合继承实现的继承,子类并没有办法区分是继承原型还是继承于构造函数,如何优化?
通过Object.create()
//优化后部分代码
Tiger.prototype = Object.create(Cat.prototype )
Tiger.prototype.constructor = Tiger
(四)原型式继承
function object(o){
// 在 object()函数内部,先创建了一个临时性的构造函数
function F(){}
// 传入的对象作为这个构造函数的原型
F.prototype = o
// 返回临时类型的一个新实例
return new F()
}
object()对传入其中的对象执行了一次浅复制。ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况Object.create()与 object()方法的行为相同
总结:只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。但是包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
(五)寄生式继承
略
(六)寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
基本实现思想:即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
实现一个简单的寄生组合继承
function object(o){
// 在 object()函数内部,先创建了一个临时性的构造函数
function F(){}
// 传入的对象作为这个构造函数的原型
F.prototype = o
// 返回临时类型的一个新实例
return new F()
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function Cat(name){
this.name = name
this.color=[1,2,3]
}
Cat.prototype.sayName = function(){
console.log('我是猫科动物'+this.name)
}
function Tiger(name,age){
Cat.call(this,name)
this.age = age
}
inheritPrototype(Tiger,Cat)
Tiger.prototype.sayAge = function(){
console.log('我的年龄是'+this.age)
}
var ins1 = new Tiger('ccc',18)
ins1.sayName()
ins1.sayAge()
高效率体现在只调用了一次 Cat构造函数