What is the reason ES6 class constructors can't be called as normal functions?

╄→尐↘猪︶ㄣ 提交于 2020-01-04 07:00:56

问题


ES6 class constructors can't be called as normal functions. According to ES6 a TypeError should be raised when this is done. I used to think that classes were just syntactic sugar for a constructor function + functions in the prototype, but this makes it slightly not so.

I'm wondering, what was the rationale behind this? Unless I missed something, it prevents calling the function with a custom this, which could be desirable for some patterns.


回答1:


A revisit to the ES6 spec shows how calling a Class function object without new is disabled by combining sections 9.2.9 and 9.2.1:

9.2.9 MakClassConstructor (F)
...
3. Set F’s [[FunctionKind]] internal slot to "classConstructor".

and when specifying the [[call]] method as opposed to the [[contruct]] method of a function:

(9.2.1) 2. If F’s [[FunctionKind]] internal slot is "classConstructor", throw a TypeError exception.

No restrictions are placed on calling function in section "11.2.3 "Function calls" of ES5.1.

So you are not missing anything: you can't use apply on a class constructor function.

The major rationale is probably both to make class extensions a fairly rigorous exercise, and to detect some early forms of error. For example you can't call Promise except as a constructor - and leaving out new before a call to Promise is a programming error. In regards extending classes, note that the constructor property of class instances is correctly set (the last class after possibly multiple extensions) and the the .prototype property of the class constructor is read only - you can't dynamically change the prototype object used to construct class instances, even though you could change the prototype property of a constructor function.

I used to think classes were syntactic sugar but have moved away from the concept.




回答2:


To recap, your two main points are

  1. ES6 class constructors can't be called as normal functions.

  2. It prevents calling the function with a custom this

The first thing to note is that from the standpoint of the runtime behavior of a class, those are to points are not functionally tied together. You could for instance allow Foo() without new but still have Foo.call({}) behave like it had been newed. The ability to call as a function can allow setting this, but it doesn't have to, the same way Foo.bind({})() would bind a this and then call the function, but the bound this would be ignored.

For the rationale behind the decision, I can't give you a primary source, but I can tell you there is one solid reason. ES6 class syntax is "syntax sugar", but not for the simplified code you likely have in your head. Take for example this snippet, given your goal.

class Parent {}

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

Child.call({});

What should this do? In ES6, super() is what actually sets this. If you try to access this before having called super(), it will throw an exception. Your example code could work with Base.call({}) since it has no parent constructor, so this is initialized up front, but as soon as you're calling a child class, this doesn't even have a value up front. If you use .call there is no where to put that value.

So then the next question is, why do child classes not get this before super()? This is because it allows ES6 class syntax to extend builtin types like Array and Error and Map and any other builtin constructor type. In standard ES5 this was impossible, though with the non-standard __proto__ in ES5 it could be simulated roughly. Even with __proto__ it is generally a performance issue to extend builtin types. By including this behavior in ES6 classes, JS engines can optimize the code so that extending builtin types works without a performance hit.

So for your questions, yes, they could allow Foo.call(), or Foo(), but it would have to ignore this either way in order to allow for extending builtin types.




回答3:


what was the rationale behind this?

It's a safeguard. When you called an ES5 function constructor without new, it did very undesirable things, failing silently. Throwing an exception helps you to notice the mistake.

Of course they could have opted for the call syntax to just work the same as construction, but enforcing the new keyword is a good thing that helps us to easily recognise instantiations.

It prevents calling the function with a custom this, which could be desirable for some patterns.

Yes, this is what fundamentally changed in ES6. The this value is initialised by the superclass, which allows subclass builtins with internal slots - see here for details. This conflicts with passing a custom this argument, and for consistency one must never allow that.



来源:https://stackoverflow.com/questions/44446650/what-is-the-reason-es6-class-constructors-cant-be-called-as-normal-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!