【问题标题】:What does anObject.prototype.constructor do?anObject.prototype.constructor 做了什么?
【发布时间】:2012-06-05 20:32:14
【问题描述】:

假设我们有这个构造函数:

var Foo = function(){
   this.x = "y";
}

FooFoo.prototype.constructor 评估为相同的函数,但 Foo.prototype.constructor = function(){this.x="z"} 似乎并没有改变 new Foo() 的结果。然而,它确实改变了

的结果
var i = new Foo();
i.constructor; // evals to function (){this.x = "z"}

这里发生了什么?我不打算使用它来做任何事情,我只是对这种语言感到好奇。

【问题讨论】:

标签: javascript


【解决方案1】:

函数的prototype 属性的constructor 属性旨在指向函数,以便您可以询问对象是什么构造了它。它是作为创建函数对象的一部分自动设置的(请参阅规范的 Section 13.2 [或 here 以获得更新的版本]。)如您所见,您可以覆盖 constructor 属性在Foo.prototype 上,如果您愿意,可以更改它,但默认情况下这就是它的用途。多年来,JavaScript 规范只说constructor 属性将存在并具有给定的默认值(function Foo() { } 将意味着默认情况下Foo.prototype.constructorFoo。)。但从 ES2015 开始,情况发生了变化,规范中的各种操作现在实际上使用了constructor 属性,例如herehereherehere

(请注意,以下内容是在 ES2015 的 class 功能添加之前编写的。以下是在 ES5 及更早版本中如何执行此操作。在 ES2015+ 中,如果您正在执行构造函数和继承层次结构,那就没有好处了执行以下操作的理由;只需使用class。[如果您不使用构造函数来构建继承层次结构——而且您不必这样做,在 JavaScript 中还有其他方法可以实现它们——您不会这样做下面使用class.])

您可以重写它是有充分理由的,这与继承有关。假设您想要一个创建基础对象的Base 构造函数和一个创建具有Base 特性以及Derived 的添加/修改的派生对象的Derived 构造函数。您看到完成的通常(尽管在我看来不是理想的)方式(缺少帮助脚本)是:

function Base() {
}
Base.prototype.foo = function() {
    console.log("I'm Base#foo");
};

function Derived() {
}
Derived.prototype = new Base(); // So we get all the `Base` stuff
Derived.prototype.bar = function() {
    console.log("I'm Derived#bar");
};

var d = new Derived();
d.foo(); // "I'm Base#foo"
d.bar(); // "I'm Derived#bar"

问题在于,现在是d.constructor === Base 而不是Derived。所以能够解决这个问题很重要:

...
Derived.prototype = new Base();                           // So we get all the `Base` stuff
Object.defineProperty(Derived.prototype, "constructor", { // Fix up
    value: Derived,
    writable: true,
    configurable: true
});
...

(旁注:所有这些管道——以及围绕超级调用的复杂性——是 ES2015+ 具有 class 语法的原因。)


请注意,以上内容并不是设置继承层次结构的理想方式。这是您通常看到的,但正如我上面所说,并不理想。只是为了完整性,在仅限于 ES5 语法的环境中,这样会更好:

function Base() {
}
Base.prototype.foo = function() {
    console.log("I'm Base#foo");
};

function Derived() {
    Base.call(this); // So Base sets up its stuff
}
Derived.prototype = Object.create(Base.prototype); // So we get all the `Base` stuff
Object.defineProperty(Derived.prototype, "constructor", {
    value: Derived,
    writable: true,
    configurable: true
});
Derived.prototype.bar = function() {
    console.log("I'm Derived#bar");
};

var d = new Derived();
d.foo(); // "I'm Base#foo"
d.bar(); // "I'm Derived#bar" 

...在 ES5 之前的环境中,您对 Object.create 使用 shim/polyfill。但同样,我不直接这样做(也不推荐这样做),我使用帮助脚本,所以它是声明性的和可重复的。

【讨论】:

  • 使用Derived.prototype = Object.create( Base.prototype ) 更简洁(使用垫片Object.create,都是一样的),它不会调用可能有.bind 调用等的构造函数。继承仍然正确建立并且 instanceof 等继续工作。 MDN 也强调了这一点(尽管他们仍然使用 new A 这很糟糕)
  • @Esailija:确实如此。这基本上就是我的脚本所做的。正如我上面所说:“...通常(尽管在我看来不是理想的)方式...” 遗憾的是,人们经常看到这种“通常”的方式。
  • 嘿,我什至没有读过:P your script 你指的是血统吗?
  • @Esailija:是的。你不会同意的,因为它没有使用 ES5 垫片(虽然它基本上确实可以做到Object.create),而且它看起来很优雅。 :-)
  • 不不,我更喜欢优雅。你不会发现我在var obj = new Obj(1) 上写var obj = Object.create( Obj ).constructor(1) :P
【解决方案2】:

.prototype.constructor 只是一个辅助属性,因此创建的对象可以轻松地使用this.constructor 引用构造函数,或者像您的情况一样i.constructor

将其任意设置为其他内容不会产生任何影响,除非对期望使用 obj.constructor 获取对象的构造函数的代码产生任何影响:

if( obj.constructor === Foo ) {
 //It's a Foo
}

所以保留它是一个很好的约定。

es5shim 依赖于 .constructor.prototype 填充 Object.getPrototypeOf

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 2016-09-10
    相关资源
    最近更新 更多