在 Javascript 中,类的继承本质就是原型链。
以下是实现继承的几种方式:
1. 借助构造函数实现继承
原理:在子类初始化的时候,执行父类构造函数,并改变其this指向为子类,相当于将父类中的成员复制一份到子类中,这样子类也就有了和父类一样的成员
Demo:
缺点:子类只能继承父类本身的成员,父类原型上的属性无法继承
2. 借助原型链实现继承
原理:让子类的原型指向父类的一个实例,这样子类实例不仅能访问父类本身的成员,父类原型上的属性也能访问
Demo:
优点:解决了构造函数继承方式,子类实例无法访问父类原型上的属性的问题
缺点:如果父类中有引用类型的数据,创建多个子类实例,修改其中一个子实例中的该引用类型数据,其他的子实例也会受影响
修改 Demo:
3. 组合继承
原理:结合构造函数继承和原型链继承
Demo:
优点:
1. 避免了构造函数继承时的子实例不能访问父类原型上的属性
2. 避免了原型链继承时的修改一个子实例引用类型数据时,其他子实例该属性受影响的问题
缺点:父构造函数被执行了两次,子实例初始化的时候被调用一次,修改子类原型的时候被执行一次
修改:避免构造函数被执行两次,而是在只在初始化的时候被执行一次
存在问题:和上面一样,其实都存在另一个问题,就是无法判别子实例到底是子类直接初始化的,还是父类初始化的
原因:因为子类原型是父类的原型,所以,子类原型中的构造器其实是指向父类的,所以,子实例的 __proto__ 中的 constructor 指向父类
再修改:
原理:
1. Object.create() 方法,返回的是一个空对象,而这个空对象的原型就是传入的参数对象
2. 给这个中间对象,也就是 Object.create 返回的空对象,丰富 constructor 属性,指向子类构造函数
3. 把这个中间对象,作为子类的原型,它的 constructor 自然是指向子类的,并且因为它本身的原型是父类的原型,所以在整个原型链上,子类自然也能访问父类原型上的属性
最终实现类型继承:
function F () {} function Z () {
F.call(this)
}
Z.prototype = Object.create(F.prototype) Z.prototype.constructor = Z