ES6类构造函数为什么不能像普通函数一样被调用?

4

ES6类构造函数不能像普通函数一样调用。根据ES6的规定,这样做应该会引发TypeError错误。我曾经认为,类只是构造函数+原型中的函数的语法糖,但这使得它稍微有些不同。

我想知道,这背后的理由是什么?除非我漏掉了什么,否则它阻止了使用自定义的this来调用函数,而某些模式可能希望这样做。


或许有帮助 https://dev59.com/_10a5IYBdhLWcg3wHlil - djfdev
这个问题可能是基于观点的,但可能是因为你不要忘记使用“new”来进行调用。最好在TC39讨论论坛上询问。 - RobG
同样的原因,它们为什么不能在几乎所有其他面向对象语言中被称为函数?因为这不是构造函数的作用。 - Heretic Monkey
3个回答

0

总结一下,你的两个主要观点是:

  1. ES6类构造函数不能像普通函数一样被调用。

  2. 它防止使用自定义的this来调用函数。

首先要注意的是,从类的运行时行为的角度来看,这些观点在功能上并没有紧密联系。例如,你可以允许 Foo() 不带 new,但仍然使 Foo.call({}) 的行为就像已经使用了 new 一样。作为函数调用的能力可以允许设置 this,但不必如此,就像 Foo.bind({})() 会绑定一个 this 然后调用函数,但绑定的 this 将被忽略一样。

至于这个决定背后的原因,我无法给你一个主要来源,但我可以告诉你有一个很好的理由。ES6类语法是“语法糖”,但不是你脑海中可能想象的简化代码。以你的目标为例,看看这个片段。

class Parent {}

class Child extends Parent {
  constructor() {
    // What is "this" here?
    super();
  }
}

Child.call({});

这段代码应该做什么?在ES6中,super()实际上是设置this的方法。如果在调用super()之前尝试访问this,它会抛出异常。你的示例代码可能可以使用Base.call({}),因为它没有父构造函数,所以this被提前初始化了,但是一旦你调用一个子类,this甚至没有一个初始值。如果你使用.call,就没有地方可以放置那个值。

那么下一个问题是,为什么子类在super()之前不会得到this?这是因为它允许ES6类语法扩展像ArrayErrorMap等任何其他内置构造函数类型。在标准的ES5中,这是不可能的,尽管在ES5的非标准__proto__中可以粗略地模拟。即使使用__proto__,扩展内置类型通常也会存在性能问题。通过在ES6类中包含此行为,JS引擎可以优化代码,以便扩展内置类型不会影响性能。
所以对于你的问题,是的,他们可以允许Foo.call()Foo(),但无论如何都必须忽略this,以便允许扩展内置类型。

0
这背后的原理是什么?
这是一种保护措施。在调用ES5的函数构造器时,如果没有使用new关键字,它会做出非常不良的行为,而且还会悄无声息地失败。抛出异常可以帮助您注意到错误。
当然,他们本可以选择让调用语法与构造语法相同,但强制使用new关键字是一件好事,可以帮助我们轻松识别实例化。
这防止了使用自定义this调用函数,这对某些模式可能是有益的。
是的,这是ES6中根本性的变化。超类初始化了this值,这允许具有内部插槽的子类内置 - 有关详细信息,请参见此处。这与传递自定义this参数冲突,为了保持一致,必须永远不允许这样做。

顺便说一下,使用Reflect.construct仍然可以实现一些理想的模式。你具体想要什么? - Bergi

0
重新审视ES6规范,结合9.2.9和9.2.1两个部分,可以看出如何禁用不使用new调用类函数对象的方法:
9.2.9 MakClassConstructor (F) ... 3. 将F的[[FunctionKind]]内部插槽设置为"classConstructor"。
并且在指定函数的[[call]]方法而不是[[construct]]方法时:
(9.2.1) 2. 如果F的[[FunctionKind]]内部插槽为"classConstructor",则抛出TypeError异常。
在ES5.1的“11.2.3“函数调用”部分没有对调用函数施加任何限制。
因此,您没有错过任何内容:您不能在类构造函数上使用apply。
主要的原因可能是为了使类扩展成为一个相当严格的练习,并检测一些早期形式的错误。例如,您不能调用Promise,除非作为构造函数 - 在调用Promise之前省略new是一种编程错误。关于扩展类,请注意类实例的constructor属性已正确设置(最后一个类,可能有多个扩展),类构造函数的.prototype属性是只读的 - 即使您可以更改构造函数的原型属性,也无法动态更改用于构造类实例的原型对象。
我曾经认为类是语法糖,但现在已经摒弃了这个概念。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接