【问题标题】:Is the `new` keyword the only way to automatically set the `constructor` property when constructing objects?`new` 关键字是构造对象时自动设置 `constructor` 属性的唯一方法吗?
【发布时间】:2016-05-01 16:32:34
【问题描述】:

我目前正在使用Object.create() 来构造这样的对象:

const Tab = ({id, windowId}) => Object.assign(Object.create(Tab.prototype), {id, windowId})

Tab.prototype = {
  constructor: Tab,
  toString: function() {
    return `${this.constructor.name} ${JSON.stringify(Object.values(this))}`
  }
}

其中一个目标是避免使用 new 关键字,以便我可以使用构造函数,例如,像这样:[{id: 1, windowId: 2}].map(Tab)(与使用像 String 这样的原生构造函数相同;例如,[1,2,3].map(String) 有效)。问题是必须手动定义构造函数属性并不好,所以有什么方法可以绕过它并自动设置构造函数,例如使用new 关键字,同时仍然使用Object.create()

更新基于答案的固定版本:

const Tab = function({id, windowId}) {
  return Object.assign(Object.create(Tab.prototype), {id, windowId})
}

Object.assign(Tab.prototype, {
  toString: function() {
    return `${this.constructor.name} ${JSON.stringify(Object.values(this))}`
  }
})

【问题讨论】:

  • 请注意,您将遇到的一个问题是(据我所知)箭头函数不会具有“名称”属性(或者它将是 undefined 或东西)。
  • 我的目标是 Chrome(扩展),它支持匿名函数的推断名称,所以它可以工作。
  • 哦,好的。我猜这有点好。
  • 只是出于好奇:你到底为什么要使用Tab.prototype(而不是单独的对象文字)?
  • 不确定我是否理解这个问题,抱歉。我使用原型是因为它在创建的对象之间共享。

标签: javascript ecmascript-6 ecmascript-5


【解决方案1】:

new operator 不会创建 constructor 属性。它只是调用[[Construct]] 内部方法。

默认情况下,实例没有任何constructor 属性。他们只从他们的[[Prototype]]继承它,也就是构造函数的prototype

prototypeconstructor 属性仅在您 create the function 时创建一次。

如果你希望能够将构造函数作为函数调用,你可以使用

function Constructor() {
  if(this instanceof Constructor) {
    // Called as a constructor
    // ...
  } else {
    // Called as a function
    return new Constructor();
  }
}

这还允许您为每种情况实现不同的行为,例如String,例如

typeof String(); // "string"
typeof new String(); // "object"`.

【讨论】:

  • 可能值得指出的是,通过class创建的构造函数不能作为函数调用。
  • 谢谢,但我宁愿完全避免使用 new 关键字。希望它在 ES7 中对类语法也是可选的。
  • @ReinisI。您可以使用var instance = Object.create(Constructor.prototype), ret = Constructor.call(instance); return Object(ret) === ret ? ret : instance; 代替new。但是为什么要避开new呢?
  • 我实际上不确定您所说的代码示例是什么意思;这会比目前的方式更好(在问题中更新)?引用 Eric Eliott 关于 new 关键字的话说:“new 关键字违反了替换原则和打开/关闭原则。它也具有破坏性,因为它为语言添加了零值,并将所有调用者与对象实例化。”
  • @ReinisI。该代码 polyfills new / [[Construct]]。完整代码为jsfiddle.net/aho9chqx
【解决方案2】:

正如 Oriol 提到的,prototypeprototype.constructor 属性是在创建函数时分配的。他的解决方案仍然包含 new 关键字,但您似乎想避免。

箭头函数不分配构造函数属性

但是,箭头函数没有自动创建 prototypeprototype.constructor 属性,它们不能使用 new 关键字进行实例化。

如果您没有特别需要坚持使用箭头函数,我建议使用经典的命名函数表达式。由于 chrome 似乎可以推断匿名函数的名称,但您可能不需要该名称。

覆盖 Tab.prototype 也会覆盖构造函数属性

您不保留.constructor 属性的另一个原因是,您在为其分配对象时覆盖了整个Tab.prototype。相反,您可以只以单一方式分配属性:

const Tab = function Tab({id, windowId}) { return Object.assign(Object.create(Tab.prototype), {id, windowId}) };

Tab.prototype.toString = function() {
    return `${this.constructor.name} ${JSON.stringify(Object.values(this))}`
  }
};

或者您可以使用Object.assign 将额外的属性添加到Tab.prototype

const Tab = function Tab({id, windowId}) { return Object.assign(Object.create(Tab.prototype), {id, windowId}) };

Object.assign(Tab.prototype, {
    toString() {
       return `${this.constructor.name} ${JSON.stringify(Object.values(this))}`
    }
});

如果您正在编写一个可公开访问的函数并希望禁止其他人在您的函数上使用new 运算符,您可以使用new.target 来阻止这种情况发生:

const Tab = function Tab({id, windowId}) {
    if (new.target) {
        throw new Error(`${ this.constructor.name } is not a constructor`);
    }
    return Object.assign(Object.create(Tab.prototype), {id, windowId})
};

// ...

【讨论】:

  • 谢谢,我的困惑源于忘记箭头函数没有原型。我已经用我最终使用的内容更新了问题。
  • 也不要忘记 Reflect.construct() :)
  • 确实如此,尽管它在许多方面类似于new 运算符(支持伪经典继承范例)。当然,除非 OP 不使用 new 的原因更具技术性。
猜你喜欢
  • 2022-06-13
  • 1970-01-01
  • 1970-01-01
  • 2020-08-08
  • 2019-05-06
  • 2015-08-12
  • 2010-12-25
相关资源
最近更新 更多