1、类与实例
1 | /** |
2、类与继承
2.1 通过构造函数实现
1 | function Parent1(){ |
call
改变函数运行上下文
缺点:Parent1 原型链上的内容无法被继承
下面我们通过一个栗子来验证:
1 | function Parent1(){ |
因此,如果父类所有属性都在构造函数中,那么可以使用此种方法,否则,父类原型链上的属性无法被子类继承。
2.2 通过原型链实现
1 | function Parent2(){ |
缺点:实例具有相同的原型对象引用,因此原型链上的属性是公用的。如果某个实例对象修改了原型链上的方法,则其他实例对象也会跟着改变。
修改代码:
1 | function Parent2(){ |
为了弥补以上两种方式的不足,我们对上述方法进行改进,得出了以下几种组合方法:
2.3 构造函数与原型链组合方法
1 | function Parent3(){ |
验证一下:
1 | var s3 = new Child3(); |
由此可见,改种方法弥补了上述两个方法的不足。但仍然有自身的缺点:
父类的构造函数被调用了两次:一次在 Parent3.call(this)
,一次在 new Parent3()
时。
2.4 构造函数与原型链组合方法的优化1
1 | function Parent4(){ |
这种方法仍然有缺点:实例对象的 constructor 属性是父类构造器,而不是子类构造器。这是由于 Child4.prototype = Parent4.prototype
导致的,Child4 没有自己的 constructor,而是继承自 Parent4,而 Parent4 的 constructor 是 Parent4。
验证一下:
1 | var s5 = new Child4(); |
事实上,方法三也有同样的问题。
2.5 构造函数与原型链组合方法的优化2
1 | function Parent5(){ |
Object.create() 方法相当于创建了一个中间对象,而这个中间对象的原型对象指向 Parent5,构成了一个原型链。同时实现了父类和子类的隔离。
但此时 Child5 仍然没有自己的 constructor,只能通过原型链来查找。因此,new Child5().constructor
仍然是 Parent5。再做修改:
1 | Child5.prototype.constructor = Child5; |
那么问题来了,在方法4中是不是也能用 Child.prototype.constructor = Child
的方法来实现呢?
因为方法4中 Child4.prototype = Parent4.prototype
,因此如果按照该方法修改,则会导致父类的 constructor 也被修改。
2.6 总结
综上所述,继承最好的方式为:
1 | function Parent(){ |