本文深入探讨JavaScript中基于原型的类继承机制,重点分析无参和有参继承场景下的常见陷阱。通过示例代码揭示直接实例化父类作为子类原型所导致的构造函数提前执行及constructor属性指向错误问题,并给出利用apply方法传递参数及遍历原型对象实现方法复制的完整解决方案。
无参类继承的问题
无参数类继承的问题
首先通过一段示例代码来演示B继承自A的实现方式:
代码如下:
function A() {
}
A.prototype.a1 = function() {
};
function B() {
}
B.prototype = new A();
B.prototype.b1 = function() {
};
var b = new B();
alert(b.constructor == A); // true
alert(b.constructor == B); // false 上述代码存在两个主要缺陷:
* 为了设置B的原型,必须实例化A,这会导致A的构造函数在B实例化前就被执行。然而,根据面向对象的设计原则,在创建B的实例之前,B及其父类A的构造函数均不应被触发。
* 直接赋值B.prototype = new A()会覆盖B原有的prototype对象,导致b.constructor指向A而非B。
有参类继承的问题
有参类继承的问题
假设A和B的构造函数均接收两个字符串参数s1和s2,其中A内部计算这两个字符串的总长度,而B则直接传递s1和s2给A:
代码如下:
function A(s1, s2) {
this.totalLength = s1.length + s2.length;
}
A.prototype.a1 = function() {
};
function B(s1, s2) {
}
B.prototype = new A();
B.prototype.b1 = function() {
};
new B("ab", "123"); 可以看出,上述实现无法将s1和s2传递给A。由于在设置原型时实例化A并未传入参数,从而引发运行时异常:
代码如下:
s1 is undefined 解决方案
解决方案
由于s1和s2仅在B的作用域内可见,要将其传递给A,必须在B内部进行操作。利用函数的apply方法可以完美解决此问题:
代码如下:
function B(s1, s2) {
A.apply(this, arguments);
alert(this.totalLength);
} 接下来需解决如何将A的方法添加到B的原型中。这可以通过遍历A.prototype并将方法复制到B.prototype来实现。需要注意的是,若存在同名方法,子类应优先(即重载),因此复制时需避免覆盖子类已定义的方法:
代码如下:
for (var m in A.prototype) {
if (!B.prototype[m]) { // 父类不能覆盖子类的方法
B.prototype[m] = A.prototype[m];
}
} 后记
后记
鉴于C#、Java等高级语言已摒弃多继承,本文仅讨论单继承场景。所述继承方案将作为jRaiser库的一个扩展功能发布。