目录
原型继承:
// '父'构造函数
function Father() {
this.name = 'foo';
this.family = ['Jack','Tom','James'];
}
Father.prototype.sayName = function () {
console.log(this.name);
};
Father.prototype.sayFamily = function () {
};
// '子'构造函数
function Child() {
}
// 实现继承
Child.prototype = new Father();
// 看看继承之后的效果
let childInstance1 = new Child();
childInstance1.sayName(); // 'foo'
childInstance1.sayFamily(); // ['Jack','Tom','James']
通过将子构造函数的原型设置为父构造函数实例的方法实现了继承,这就是原型链继承。在这个例子中,子构造函数的实例本身是没有 name 和 family 属性的,这两个属性都是通过原型链在子构造函数的原型上找到的,而子构造函数的原型来自于父构造函数的一个实例。 子构造函数的实例本身也是没有 sayName 和 sayFamily 方法的,沿着原型链查找,子构造函数的原型中也没有这两个方法,再向上查找,这两个方法来自与父构造函数的原型上。
原型链继承的问题:
通过这种继承方法,有一个比较明显的问题,我们知道,引用类型的原型属性会被所有的实例共享,那么在上面的例子中,由于子构造函数的原型是父构造函数的实例,所以 family 属性(数组是引用类型)会被子构造函数的所有实例共享。也就是说,当我们修改了子构造函数一个实例上的 family 属性时,会对子构造函数原型上的该属性直接产生影响,从而影响到子构造函数的全部实例。
// 修改subInstance2的属性值,看看会有什么影响
let childInstance2 = new Child();
childInstance2.name = 'bar';
childInstance2.family.push('curry');
childInstance1.sayName(); // 'foo'
childInstance1.sayFamily(); // ['Jack','Tom','James','curry']
childInstance2.sayName(); // 'bar'
childInstance2.sayFamily(); // ['Jack','Tom','James','curry']
我们看到,修改了 childInstance2 的 name 属性(字符串是基础类型),不会对其他实例产生影响,但是当我们修改了 family 属性时,就会造成 childInstance1 的 family 属性也被修改了。这显然会带来很多的麻烦。
组合继承:
思路是通过原型链实现对原型属性和方法的继承,但是利用构造函数实现对实例属性的继承,避免属性被共享。
// '父'构造函数
function Father() {
this.family = ['Jack','Tom','James'];
}
Father.prototype.sayFamily = function () {
console.log(this.family);
};
// '子'构造函数
// 这里利用构造函数把父构造函数的属性直接放到子构造函数属性中
function Child() {
Father.call(this);
}
// 实现继承
Child.prototype = new Father();
// 看看继承之后的效果
let childInstance1 = new Child();
childInstance1.sayFamily(); // ['Jack','Tom','James']
// 修改subInstance2的属性值,看看会有什么影响
let childInstance2 = new Child();
childInstance2.family.push('curry');
childInstance1.sayFamily(); // ['Jack','Tom','James']
childInstance2.sayFamily(); // ['Jack','Tom','James','curry']
采用组合继承,子构造函数创造出来的实例本身就有 family 属性(构造函数具有的属性和方法都在实例本身),不必再通过原型链向原型查找,也就避免了属性被共享的问题。