继承
很多面向对象的语言都支持两种继承:接口继承和实现继承。前者知识继承方法签名,后者继承实际的方法。接口继承在ECMAScript中是不可能的,因为函数没有签名。实现继承是ECMAScript唯一支持的继承方式,主要通过原型链实现的
原型链继承
重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性constructor属性指回构造函数,而实例有一个内部指针指向原型,如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链
实现原型链继承代码
<script>
//被继承的函数
function Super () {
this.like = {
1:"play",
2:"read"
}
this.name = 123
}
//往super原型中添加方法
Super.prototype.getName = function () {
console.log(this.name)
}
function Sub () {
}
//通过原型链实现继承
//Sub的显式原型指向Super的实例,即可以通过原型链访问到Super原型中的方法
Sub.prototype = new Super()
var a = new Sub()
console.log(a.getName) //funcition
</script>
原型链继承也有问题,问题出现在原型中包含引用值的时候。原型中包含的引用值会在所有实例间共享
例子
<script>
function Super () {
this.like = ['red','yellow','black']
}
function Sub () {
}
Sub.prototype = new Super()
var a = new Sub()
a.like.unshift('green')
var c = new Sub()
//尽管c中没有添加green,但c中还是有green,因为引种值在实例中共享
console.log(a.like,c.like)
</script>
盗用构造函数
为了解决原型包含引用值导致继承问题,盗用构造函数流行起来,基本思路很简单,在子类构造函数中调用父类构造函数
例子
<script>
function Super () {
this.like = ['red','yellow','black']
this.name = 123
}
function Sub () {
Super.call(this)
}
Sub.prototype = new Super()
var a = new Sub()
a.like.unshift('green')
var c = new Sub()
//此时a和c的输出就不是相同的了
console.log(a.like,c.like)
</script>
通过使用call()或者apply()方法,Super构造函数在为Sub的实例创建新的对象的上下文中执行了。想到与Sub对象上运行了Super函数中所有初始化代码,结果就是每个实例会有自己的属性
组合继承
将上面两者优点集中了起来,基本思路使用原型链上的属性和方法,而通过盗用构造函数来继承实例属性。
<script>
function Super () {
this.like = ['red','yellow','black']
this.name = 123
}
Super.prototype.getName = function () {
console.log(this.name)
}
function Sub () {
Super.call(this)
}
Sub.prototype = new Super()
var a = new Sub()
a.like.unshift('green')
var c = new Sub()
a.name = 456
console.log(a.like,c.like)
console.log('--------')
console.log(a.name,c.name)
console.log('--------')
console.log(a.getName,c.getName)
</script>