Constructor behaving differently using ES6 shorthand notation

599 views Asked by At

ES6 introduced a shorthand notation to initialize objects with functions and properties.

// ES6 shorthand notation
const obj1 = {
    a(b) {
        console.log("ES6: obj1");
    }
};

// ES5
var obj2 = {
    a: function a(b) {
        console.log("ES5: obj2");
    }
};

obj2.a();
obj1.a();

new obj2.a();
new obj1.a();

However, these different notations behave differently, as you can see. If I do new obj1.a() in the browser (tested Chrome and Firefox), I get a TypeError: obj1.a is not a constructor. new obj2.a() behaves completely normally.

What happens here? Does anyone have an explanation, and/or links to documentation/specification?

3

There are 3 answers

0
Nebula On BEST ANSWER

The specification isn't very direct about explaining this, but we can follow a short chain..

We'll start at EvaluateNew, since that's the behaviour we're wondering about. Step 7 is clearly the one we're looking for here:

  1. If IsConstructor(constructor) is false, throw a TypeError exception.

So IsConstructor is where we need to look next.

Both the summary and the steps describe this:

The abstract operation IsConstructor determines if argument, which must be an ECMAScript language value, is a function object with a [[Construct]] internal method.


  1. If Type(argument) is not Object, return false.
  2. If argument has a [[Construct]] internal method, return true.
  3. Return false.

So, judging by the looks of it, our obj1.a doesn't have a [[Construct]] internal method. Let's look for where it says that it shouldn't have one..

Here's what we're looking for, PropertyDefinitionEvaluation. The first step is useful here:

Let methodDef be DefineMethod of MethodDefinition with argument object.

That calls DefineMethod with just one argument, object. Let's look at DefineMethod - here's what we need:

With parameters object and optional parameter functionPrototype.


  1. If functionPrototype was passed as a parameter, let kind be Normal; otherwise let kind be Method.
  2. Let closure be FunctionCreate(kind, [more arguments snipped]).

Since functionPrototype was not passed as a parameter, the kind is Method. Let's look at what FunctionCreate does with that:

  1. If kind is not Normal, let allocKind be "non-constructor".
  2. Else, let allocKind be "normal".
  3. Let F be FunctionAllocate([other arguments snipped], allocKind).

Now we're getting close! We just need to look at FunctionAllocate does with allocKind (which is "non-constructor" as per the above steps), which is what gives a function all of its internal methods and such.

  1. If functionKind is "normal", let needsConstruct be true.
  2. Else, let needsConstruct be false.

  1. Let F be a newly created ECMAScript function object with the internal slots listed in Table 27. All of those internal slots are initialized to undefined.

  1. If needsConstruct is true, then

    a. Set F's [[Construct]] internal method to the definition specified in 9.2.2.

    b. Set the [[ConstructorKind]] internal slot of F to "base".

Finally! If we go through the relevant steps, we can see since functionKind isn't "normal", needsConstruct becomes false, and so a [[Construct]] internal method is never assigned! Then IsConstructor sees that and returns false, and so EvaluateNew fails.

MDN describes this behaviour very simply:

All method definitions are not constructors and will throw a TypeError if you try to instantiate them.

..but now you know how they aren't constructors, officially.

6
williamsandonz On

Methods declared using this syntax are not intended to be constructable

Reference here

0
Haoqun Jiang On

Seems the original discussion happens here: https://github.com/rwaldron/tc39-notes/blob/master/es6/2012-07/july-26.md

MM: three reasons for this:

  • precedent in builtins
  • using a method as a constructor is generally nonsense
  • to freeze a class, I have to freeze the .prototype of the methods on the prototype!!

and

AWB: suggestion: concise methods should be the same for both classes and object literals

  • strictness
  • enumerability
  • constructability
  • attributes

That's how both class methods and object methods became non-constructable