JavaScript继承:当构造函数有参数时

时间:2011-09-23 18:44:12

标签: javascript inheritance prototype-programming

使用纯JavaScript进行继承,这就是我通常做的事情:

function A() {}
A.prototype.run = function () {};

function B() {}
B.prototype = new A;
B.prototype.constructor = B;

由于没有参数传递给构造函数,因此新的A没有什么可抱怨的。现在,如果构造函数有要传递的参数,我还没有想出一个很好的继承方法。例如,

function A(x, y) {}
A.prototype.run = function () {};

function B(x, y) {}
B.prototype = new A;
B.prototype.constructor = B;

我可以传递一些任意值,如:

B.prototype = new A(null, null);

在某些情况下,我可能需要在A的构造函数中验证x和y。在某些极端情况下,我在检查x或y时需要抛出错误。然后,B无法使用新的A继承A.

有什么建议吗?

谢谢!

4 个答案:

答案 0 :(得分:22)

好吧,如果你想让B.prototype一个继承自A.prototype的对象,而不执行A构造函数,以避免所有可能的副作用,你可以使用一个虚拟构造函数这样做,例如:

function tmp() {}
tmp.prototype = A.prototype;
B.prototype = new tmp();
B.prototype.constructor = B;

您可以创建一个函数来封装创建这个新对象的逻辑,例如:

function inherit(o) {
  function F() {}; // Dummy constructor
  F.prototype = o; 
  return new F(); 
}

//...
B.prototype = inherit(A.prototype);
B.prototype.constructor = B;

如果您定位现代浏览器,您可以将ECMAScript 5 Object.create方法用于相同目的,例如:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
//..

答案 1 :(得分:6)

问题在于您无法轻松地为B创建原型对象,因为无法调用A的构造函数。这是由于在执行new B之前构造函数的参数未知。您需要一个虚拟构造函数来构造B的原型,该原型链接到A的原型。

B.prototype = (function(parent){
    function protoCreator(){};
    protoCreator.prototype = parent.prototype;
    // Construct an object linking to A.prototype without calling constructor of A
    return new protoCreator();
})(A);

设置B原型对象后,需要确保在A的构造函数中调用B的构造函数。

function B(x, y) {
    // Replace arguments by an array with A's arguments in case A and B differ in parameters
    A.apply(this, arguments);
}

您现在应该可以通过调用B来实例化new B(x, y)

有关A中包含参数验证的完整内容,请参阅a jsFiddle

在原始代码中,您正在设置B.prototype.constructor = B。我不明白你为什么要这样做。 constructor属性不会影响prototype属性负责的继承层次结构。如果你想在constructor属性中包含命名构造函数,你需要从上面扩展代码:

// Create child's prototype – Without calling A
B.prototype = (function(parent, child){
    function protoCreator(){
        this.constructor = child.prototype.constructor
    };
    protoCreator.prototype = parent.prototype;
    return new protoCreator();
})(A, B);

使用B.prototype的第一个定义,您将获得以下结果:

var b = new B(4, 6);
b.constructor // A
console.info(b instanceof A); // true
console.info(b instanceof B); // true

使用extended version,您将获得:

var b = new B(4, 6);
b.constructor // B
console.info(b instanceof A); // true
console.info(b instanceof B); // true

不同输出的原因是instanceof跟进b的整个原型链,并尝试为A.prototypeB.prototype找到匹配的原型对象(在另一个电话)。 b.constructor原型确实引用了用于定义实例原型的函数。如果你想知道为什么它没有指向protoCreator这是因为它的原型在创建A.prototype期间被B.prototype覆盖了。 the updated example中显示的扩展定义修复了此constructor属性,指向更合适(因为可能更期望)的函数。

对于日常使用,我建议完全放弃使用实例的constructor属性的想法。相反,请使用instanceof,因为其结果更可预测/预期。

答案 2 :(得分:5)

考虑一下:

function B( x, y ) {
    var b = Object.create( new A( x, y ) );

    // augment b with properties or methods if you want to

    return b;
}

然后

var b = new B( 12, 13 );

现在b继承自A的实例,后者继承自A.prototype

现场演示: http://jsfiddle.net/BfFkU/


IE8中没有实现

Object.create,但可以轻松地手动实现它:

if ( !Object.create ) {
    Object.create = function ( o ) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

这可以放在ie8.js文件中,该文件仅通过条件注释加载到IE8及以下版本。

答案 3 :(得分:4)

虽然这是一个古老的话题,但我认为无论如何我都会回应。 有两种方法可以做到:

尽管 Pseudo Classical 方式最受欢迎,但由于它需要在子构造函数中调用父构造函数一次并且在继承原型时需要调用一次,因此它有其缺点。此外,子进程的原型将包含父构造函数的所有属性,无论如何在调用子构造函数时都会被覆盖。我个人的选择是 Prototypal Inheritance

1。伪经典继承:

function A(x, y) {} 
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = new A();
B.prototype.constructor = B;

2。原型继承:

function A(x, y) {}
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;