继承
ES中只支持实现继承,且主要依靠原型链来实现的。
原型链
首先明确构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,二实例都包含一个指向原型对象内部的指针。
如果原型对象等于另一个类型的实例,那么相应的,另一个原型中也包含着一个指向另一个构造函数的指针,如此嵌套就构成了实例与原型的链条。
function SuperType() {
this.property = true
}
SuperType.prototype.getSuperValues = function() {
return this.property
}
function SubType() {
this.subproperty = false
}
//继承了SuperType
SubType.prototype = new SuperType()
SubType.prototype.getSubValues = function() {
return this.subproperty
}
var instance = new SubType()
alert(instance.getSuperValue())//true
上述代码定义了两个类型:SuperType和SubType。每个类型分别有一个属性和一个方法。它们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。本质就是重写原型对象。
1.默认的原型
所有引用类型默认继承了Object,而这个继承也是通过原型链实现的。因此默认原型都会包含一个内部指针,指向Object.prototype。这就是自定义类型都会继承toString()、valueOf()等默认方法的根本原因。
2.确定原型和实例的关系
如何确定原型和实例之间的关系:
第一,使用instanceof
操作符,只要使用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true
第二,使用isPrototypeOf()
方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
3.定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样会重写原型链。
上述代码中SubType和SuperType之间已经没有关系了。
4.原型链的问题
第一,包含引用类型值的原型属性会被所有实例共享,这就是为什么要在构造函数中,而非在原型对象中定义属性的原因
function SuperType() {
this.colors = ['red','blue','yellow']
}
function SubType() {
}
SubType.prototype = new SuperType()
let instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1) //'red','blue','yellow','black'
let instance2 = new SubType()
console.log(instance2)//'red','blue','yellow','black'
第二,在创建子类型的实例时,不能向超类型的构造函数中传递参数。
借用构造函数
在解决原型中包含引用类型所带来问题的过程中,开发人员开始使用一种叫借用构造函数的技术(也称伪造对象或经典继承),基本思想就是在子类型构造函数的内调用超类型构造函数,通过apply()
和call()
也可以在新创建的对象上执行构造函数。
function SuperType() {
this.colors = ["red","blue","green"]
}
function SubType() {
//继承了SuperType
SuperType.call(this)
}
var instance1 = new SubType()
instance1.colors.push("black")
alert(instance1.colors)//"red","blue","green","black"
var instance2 = new SubType()
alert(instance2.colors)//"red","blue","green"
上述代码中每次创建SubType对象上执行SuperType函数中定义的所有对象初始化代码。因此每个SubType都会有自己的colors属性。
1.传递参数:
相对于原型链而言,借用构造函数的一大优势就是可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name) {
this.name = name
}
function SubType() {
SuperType.call(this,"Nicholas")
this.age = 29
}
var instance = new SubType()
alert(instance.name)//"Nicholas"
alert(instance.age)//29
2.借用构造函数的问题
方法都在构造函数中定义,因此不存在函数复用。而且在超类型的原型中定义的方法,对子类型而言也是不可见的。
组合继承
也称经典继承,指将原型链和借用构造函数的技术组合。思路是使用原型链实现对原型属性和方法的继承,二通过借用构造函数实现对实例属性的继承。
function SuperType(name) {
this.name = name
this.colors = ["red","blue","green"]
}
SuperTye.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("Nicholas",29)
instance1.colors.push('black')
alert(instance1.colors)//"red,blue,green,black"
instance1.sayName()//Nicholas
instance1.sayAge() //29
var instance2 = new SubType("Greg",27)
alert(instance2.colors)//"red,blue,green"
alert(instance2.name) //Greg
alert(instance2.sayAge())//27
组合继承避免了原型链和借用构造函数的缺陷,是Js中最常用的继承模式。而且,instanceof
和isPrototypeOf()
也可用于识别基于组合继承创建的对象。
组合继承最大的问题是无论什么情况下都会调用两次超类型构造函数:第一次在创建子类型原型时,另一次是在子类型构造函数内部。
原型式继承
var person = {
name: 'Nicholas',
firends:['Shelby','Court','Van']
}
var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push("Rob")
var yetAnotherPerson = object(person)
yetAnotherPerson.name = "Linda"
yetAnoterPerson.friends.push("Barbie")
alert(person.friends) //'Shelby,Court,Van,Rob,Barbie'
ES5新增了Object.create()
方法规范了原型式继承。
var person = {
name: 'Nicolas',
friends: ['Shelby','Court']
}
var anotherPerson = Object.create(person, {
name: {
value: 'Greg'
}
})
//如果传入第二个参数就会覆盖原型对象上的同名属性。
寄生式继承
function createAnother(original) {
var clone = object(original)
clone.sayHi = function() {
alert('h1')
}
return clone
}
var person = {
name: 'Nicholas',
friends: ['Shelby','Court']
}
var anotherPerson = createAnoter(person)
anotherPerson.sayHi() //'hi'
使用寄生式继承来为对象添加函数,由于不能做到函数复用而降低效率,只一点与构造函数模式类似
寄生组合式继承
为了弥补组合继承的不足。
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function() {
alert(this.name)
}
function SubType(name,age) {
Su[erType.call(this,name)
this.age = age
}
function inheritPrototype(subType,superType) {
var prototype = Object(superType.prototype)
prototype.constructor = subType //增强对象
subType.prototype = prototype //指定对象
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function() {
alert(this.age)
}