什么是继承
简单来说,就是一个对象使用另一个对象的属性和方法,并且不要影响原对象的功能。
构造函数的继承
属性继承
一般属性继承是通过call或apply方法,将父对象的构造函数绑定在子对象上
有一个父对象:
1 | function Father(hobby, lastName){ |
有一个子对象需继承父对象的属性:
1 | function Son(hobby, lastName){ |
方法继承
方法的继承是通过原型(prototype)来完成
直接继承prototype
还是上面的例子,我们在Father
的原型上添加一个showHobby
的方法:
1 | function Father(hobby, lastName){ |
现在子对象需要继承这个方法,可以将子对象的原型指向父对象,即:
1 | function Son(hobby, lastName){ |
此时Son
的实例上就有showHobby
方法了:
1 | var son = new Son('reading','wang'); |
现在虽然实现了方法的继承,但是上面这个方法还是有问题的:
第一个问题:constructor的指向问题
constructor是对象的一个属性,指向它的构造函数,每个prototype对象上都有这个属性
按理说,Son
的实例,即son
,它的构造函数就是Son
,然而在这里,它指向了Father:
1 | son.constructor == Father |
出现这样的原因也很明了,因为Son
的原型对象指向了Father
的原型对象,Father
的prototype
上的constructor
显然是Father
。
这结果会导致继承链的紊乱,son
明明是Son
构造的。所以我们需要手动修改constructor
指向,即添加:
1 | Son.prototype.constructor = Son; |
第二个问题:任何对Son.prototype的修改都反应到了Father.prototype上
我们在Son
的原型上添加一个name
属性:
1 | Son.prototype.name = 'John'; |
查看Father
原型的变化:
1 | Father.prototype |
现在,Father
的原型上也有了name
属性,且值也为john
,而且第一个问题中的在Son
原型上修改constructor
也会反应到Father
上,现在Father
实例上的constructor
也指向了Son
。
这下更乱套了,所以为了同时解决这两个问题,有个改进的方法。
利用空对象做中介
整个例子完整代码:
1 | function Father(hobby, lastName){ |
关键部分就是:
1 | //创建中介空对象,并将它的原型指向 Father的原型 |
这样就可以无错地实现了构造函数的继承。