带你看懂JavaScript的继承(四) 借用构造函数

本期我们正式开始介绍JS的继承方法。在上一期中,我们讲了原型链的原罪,引用类型值在继承中所引发的问题。大家不熟悉可以翻翻上一期的文章。解决这个问题的方法就是采用这种叫做借用构造函数的技术。也就是我们说的伪造对象或者经典继承

基本的思想就是在子类型构造函数的内部调用超类型的构造函数。在JS中,你的思维要足够的灵活,从全局的角度去思考问题。函数是什么?它就是在特定环境中执行代码的对象。因此,通过使用apply()和call()方法也可以在新创建的对象上执行构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SuperType(){
this.colors = ["red","blue"];
}

function SubType(){
SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //["red","blue","black"]

var instance2 = new SubType();
console.log(instance2.colors); //["red","blue"]

我们在SubType的实例中使用call方法调用了SuperType中的构造函数,这样,新的SubType就会执行SuperType函数中定义的所有对象初始化代码。结果就是SubType的每个实例都会具有自己的colors属性副本。这样我们就解决了引用类型值在继承中所产生的共享问题。
What is more?借用构造函数的另外一个优势的地方在于,可以在子类型构造函数中想超类型构造函数传参,我们就解决了上一篇所讲过的原型链原罪。Talk is cheap,show you the code:

1
2
3
4
5
6
7
8
9
10
11
12
function SuperType(name){
this.name = name;
}

function SubType(){
SuperType.call(this,'XUE Shuai');
this.age = 18;
}

var instance = new SubType();
console.log(instance.name); //'XUE Shuai'
console.log(instance.age); //18

SuperType接收一个参数name,SubType构造函数内部调用SuperType构造函数,为SubType的实例设置了name属性。需要注意的一点是为了防止SuperType的构造函数重写子类型的的属性,可以再调用超类型构造函数后,再添加应该在子类型中定义的属性

####借用构造函数继承的问题
问题的缘由其实是构造函数引发的。我们思考这样一个问题:当你在超类型的构造函数中定义很多方法的时候,我们采用借用构造函数的技术来实现继承,这时候会有什么问题?很明显,我们会在子类型的每个实例中都重新定义这些方法,这样我们就无法实现函数的复用。这是我们不希望看到的。而且超类型原型中定义的方法,对于子类型而言,是不可见的。

这个问题是可以解决的,这里留一个思考题,运用我们目前所学的知识,来解决这个弊端。下一期会我们会讨论这个解决方案。