JavaScript继承和构造函数属性

时间:2011-11-11 11:05:41

标签: javascript inheritance constructor instanceof

请考虑以下代码。

function a() {}
function b() {}
function c() {}

b.prototype = new a();
c.prototype = new b();

console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
console.log((new c()).constructor); //a()
  • 为什么不为b和c更新构造函数?
  • 我做遗产错了吗?
  • 更新构造函数的最佳方法是什么?

此外,请考虑以下事项。

console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
  • 鉴于(new c()).constructor等于a()Object.getPrototypeOf(new c())a{ }instanceof如何知道new c()c {{1}}的实例?
  

http://jsfiddle.net/ezZr5/

3 个答案:

答案 0 :(得分:64)

好的,让我们玩一个小小的心灵游戏:

从上图中我们可以看到:

  1. 当我们创建像function Foo() {}这样的函数时,JavaScript会创建一个Function实例。
  2. 每个Function实例(构造函数)都有一个属性prototype,它是一个指针。
  3. 构造函数的prototype属性指向其原型对象。
  4. 原型对象有一个属性constructor,它也是一个指针。
  5. 原型对象的constructor属性指向其构造函数。
  6. 当我们创建Foo的新new Foo()实例时,JavaScript会创建一个新对象。
  7. 实例的内部[[proto]]属性指向构造函数的原型。
  8. 现在,问题出现了为什么JavaScript不将constructor属性附加到实例对象而不是原型。考虑:

    function defclass(prototype) {
        var constructor = prototype.constructor;
        constructor.prototype = prototype;
        return constructor;
    }
    
    var Square = defclass({
        constructor: function (side) {
            this.side = side;
        },
        area: function () {
            return this.side * this.side;
        }
    });
    
    var square = new Square(10);
    
    alert(square.area()); // 100
    

    正如您所看到的,constructor属性只是原型的另一种方法,如上例中的areaconstructor属性的特殊之处在于它用于初始化原型的实例。否则它与原型的任何其他方法完全相同。

    在原型上定义constructor属性是有利的,原因如下:

    1. 这在逻辑上是正确的。例如,考虑Object.prototypeconstructor的{​​{1}}属性指向Object.prototype。如果在实例上定义了Object属性,则constructor将为Object.prototype.constructor,因为undefinedObject.prototype的实例。
    2. 与其他原型方法的处理方式没有区别。这使得null的工作更容易,因为它不需要在每个实例上定义new属性。
    3. 每个实例共享相同的constructor属性。因此它很有效率。
    4. 现在,当我们谈论继承时,我们有以下情况:

      从上图中我们可以看到:

      1. 派生构造函数的constructor属性设置为基础构造函数的实例。
      2. 因此,派生构造函数实例的内部prototype属性也指向它。
      3. 因此,派生的构造函数实例的[[proto]]属性现在指向基础构造函数。
      4. 对于constructor运算符,与普遍认为的相反,它不依赖于实例的instanceof属性。从上面我们可以看出,这会导致错误的结果。

        constructor运算符是二元运算符(它有两个操作数)。它在实例对象和构造函数上运行。作为Mozilla Developer Network的解释,它只是执行以下操作:

        instanceof

        简单地说,如果function instanceOf(object, constructor) { while (object != null) { if (object == constructor.prototype) { //object is instanceof constructor return true; } else if (typeof object == 'xml') { //workaround for XML objects return constructor.prototype == XML.prototype; } object = object.__proto__; //traverse the prototype chain } return false; //object is not instanceof constructor } 继承自Foo,那么Bar实例的原型链就是:

        1. Foo
        2. foo.__proto__ === Foo.prototype
        3. foo.__proto__.__proto__ === Bar.prototype
        4. foo.__proto__.__proto__.__proto__ === Object.prototype
        5. 如您所见,每个对象都继承自foo.__proto__.__proto__.__proto__.__proto__ === null构造函数。当内部Object属性指向[[proto]]时,原型链结束。

          null函数只是遍历实例对象的原型链(第一个操作数),并将每个对象的内部instanceof属性与构造函数的[[proto]]属性进行比较(第二个操作数)。如果匹配,则返回prototype;如果原型链结束,则返回true

答案 1 :(得分:12)

默认情况下,

function b() {}

然后b.prototype具有.constructor属性,该属性自动设置为b。但是,您目前正在覆盖原型,从而丢弃该变量:

b.prototype = new a;

然后b.prototype不再具有.constructor属性了;它被覆盖了。 继承自a(new a).constructor === a,因此(new b).constructor === a(它指的是原型链中的相同属性)。

最好的办法是简单地手动设置:

b.prototype.constructor = b;

你也可以为此做一点功能:

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}

http://jsfiddle.net/79xTg/5/

答案 2 :(得分:5)

constructor是函数对象的prototype属性的默认值的常规非枚举属性。因此,分配给prototype将失去财产。

instanceof仍然有用,因为它不使用constructor,而是扫描对象的原型链以获取函数prototype属性的(当前)值,即{{ 1}}相当于

foo instanceof Foo

在ECMAScript3中,没有办法设置一个var proto = Object.getPrototypeOf(foo); for(; proto !== null; proto = Object.getPrototypeOf(proto)) { if(proto === Foo.prototype) return true; } return false; 属性,其行为与内置属性相同,因为用户定义的属性总是可枚举的(即constructor可见)。

这改变了ECMAScript5。但是,即使您手动设置for..in,您的代码仍然存在问题:特别是,将constructor设置为parent-'class'的实例是一个坏主意 - 父构造函数不应该在定义child-'class'时调用,而是在创建子实例时调用。

这是一些ECMAScript5示例代码,用于完成该操作:

prototype

如果您坚持使用ECMAScript3,则需要使用自定义clone() function代替function Pet(name) { this.name = name; } Pet.prototype.feed = function(food) { return this.name + ' ate ' + food + '.'; }; function Cat(name) { Pet.call(this, name); } Cat.prototype = Object.create(Pet.prototype, { constructor : { value : Cat, writable : true, enumerable : false, configurable : true } }); Cat.prototype.caress = function() { return this.name + ' purrs.'; }; ,并且无法使Object.create()无法枚举:

constructor