JavaScript继承和构造函数属性

Quo*_*ons 36 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()
Run Code Online (Sandbox Code Playgroud)
  • 为什么不为b和c更新构造函数?
  • 我做遗传错了吗?
  • 更新构造函数的最佳方法是什么?

此外,请考虑以下内容.

console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
Run Code Online (Sandbox Code Playgroud)
  • 鉴于这(new c()).constructor等于a()Object.getPrototypeOf(new c())a{ },怎么可能instanceof知道这new c()是一个实例c

http://jsfiddle.net/ezZr5/

Aad*_*hah 64

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

从上图中我们可以看到:

  1. 当我们创建一个类似的函数时function Foo() {},JavaScript会创建一个Function实例.
  2. 每个Function实例(构造函数)都有一个属性prototype,它是一个指针.
  3. prototype构造函数的属性指向其原型对象.
  4. 原型对象有一个属性constructor,它也是一个指针.
  5. constructor原型对象的属性指向其构造函数.
  6. 当我们创建Foolike 的新实例时new Foo(),JavaScript会创建一个新对象.
  7. [[proto]]实例的内部属性指向构造函数的原型.

现在,问题出现了为什么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
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 这在逻辑上是正确的.例如考虑Object.prototype.点的constructor属性.如果财产上的实例定义,那么会因为是的一个实例.Object.prototypeObjectconstructorObject.prototype.constructorundefinedObject.prototypenull
  2. 它与其他原型方法的处理方式没有区别.这使得工作new更容易,因为它不需要constructor在每个实例上定义属性.
  3. 每个实例共享相同的constructor属性.因此它很有效率.

现在,当我们讨论继承时,我们有以下场景:

从上图中我们可以看到:

  1. 派生的构造函数的prototype属性设置为基础构造函数的实例.
  2. 因此,[[proto]]派生构造函数实例的内部属性也指向它.
  3. 因此,constructor派生的构造函数实例的属性现在指向基础构造函数.

至于instanceof运营商,与流行的看法相反,它不依赖于constructor实例的属性.从上面我们可以看出,这将导致错误的结果.

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

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
}
Run Code Online (Sandbox Code Playgroud)

简单地说,如果Foo继承自Bar,那么实例的原型链Foo将是:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

如您所见,每个对象都继承自Object构造函数.当内部[[proto]]属性指向时,原型链结束null.

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

  • 另见http://joost.zeekat.nl/constructors-considered-mildly-confusing.html (4认同)
  • +1虽然我更喜欢`Object.getPrototypeOf`而不是`.__ proto__`. (3认同)
  • 为了便于解释,我只使用了`__proto__`属性.这就是它在Mozilla开发者网络上的解释方式.尽管如此,`__ proto__`属性确实优于`Object.getPrototypeOf`方法,而且速度更快(没有函数调用开销)并且它由所有主流浏览器实现.我使用`Object.getPrototypeOf`的唯一原因是解决像Rhino这样不支持`__proto__`属性的实现.我们都有自己的喜好.我更喜欢`__proto__`属性,因为它更具可读性.干杯.=) (3认同)

pim*_*vdb 12

默认情况下,

function b() {}
Run Code Online (Sandbox Code Playgroud)

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

b.prototype = new a;
Run Code Online (Sandbox Code Playgroud)

然后再b.prototype没有.constructor财产了; 它被覆盖了.它确实继承了a,(new a).constructor === a因此(new b).constructor === a(它指的是原型链中的相同属性).

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

b.prototype.constructor = b;
Run Code Online (Sandbox Code Playgroud)

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

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}
Run Code Online (Sandbox Code Playgroud)

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


Chr*_*oph 5

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

instanceof仍将工作,因为它不使用constructor,而是扫描对象的原型链为函数的prototype属性的(当前)值,即foo instanceof Foo相当于

var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto)) {
    if(proto === Foo.prototype)
        return true;
}
return false;
Run Code Online (Sandbox Code Playgroud)

在ECMAScript3中,没有办法设置一个constructor与内置属性相同的属性,因为用户定义的属性总是可枚举的(即可见for..in).

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

这是一些ECMAScript5示例代码,用于完成它的工作方式:

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.';
};
Run Code Online (Sandbox Code Playgroud)

如果您坚持使用ECMAScript3,则需要使用自定义clone()函数,Object.create()不能使用constructor不可枚举的函数:

Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;
Run Code Online (Sandbox Code Playgroud)