Constructor definition

ECMA-262 states that a constructor is an object that supports the [[Construct]] internal method. I suspect that BigInt and Symbol support [[Construct]] because they are included in Constructor Properties of the Global Object. However, both return a TypeError when called with the new operator:

try {
  console.log(typeof new BigInt(5));
} catch (e) {
  console.log(`${e.name}: ${e.message}`); // TypeError: BigInt is not a constructor
}

try {
  console.log(typeof new Symbol(5));
} catch (e) {
  console.log(`${e.name}: ${e.message}`); // TypeError: Symbol is not a constructor
}

Note that the error includes is not a constructor.

It is confusing to me and perhaps to others that a constructor is an object that supports the [[Construct]] internal method, and BigInt and Symbol support this internal method, but BigInt and Symbol are not constructors (per the error messages), and neither support the new operator.

(Note: I do understand why BigInt and Symbol don't support the new operator, and why Boolean and perhaps others really shouldn't either.)

Why aren't BigInt and Symbol just function objects that do not support [[Construct]] and do not have a seemingly unnecessary prototype property? If they were, then the current definition would suffice.

Thanks!

From the point of view of the spec, BigInt and Symbol are constructors, and have a [[Construct]] internal method, but its behavior is to return a TypeError. So for (most?) practical purposes, they look like not-constructors.

1 Like

The .prototype is not unnecessary, it is used to supply the methods and getters on BigInt and Symbol objects.

1 Like

Another way to look at it is that they are private constructors.

const i = Object(1n);
i instanceof BigInt; true

If #constructor became part of the language perhaps BigInt and Symbol would be modelled that way.

1 Like

Like all objects, BigInt and Symbol have a [[Prototype]] internal slot which points to a parent object from which the objects inherit properties. [[Prototype]] is different than a prototype property which constructors have. A constructor’s prototype property points to the object that will become the [[Prototype]] value of any instances that it creates. But, and this is my point, BigInt and Symbol don't create instances because they are incapable of acting like constructors. That's why I'm suggesting that there is no need for BigInt and Symbol to have a prototype property. I diagrammed this distinction in Diagrams of JavaScript constructors.

As per Constructor definition - #3 by bergus and Constructor definition - #4 by aclaymore, while new Symbol() doesn't create an instance of Symbol. These instances do exist and the .prototype is used:

const s = Symbol("abc");
Object.getPrototypeOf(s) === Symbol.prototype;

Reflect.ownKeys(Symbol.prototype).includes("description"); // true
s.description; // "abc"

Yes, were talking about .prototype here. Those objects are still getting used as the [[prototype]] for instances that are getting created temporarily when accessing a property on primitives, such as Symbol("example").description or 1n.toString(). You can also explicitly construct them using Object(Symbol("example")) or Object(1n).

The spec only defines the type of Error that is thrown, the error message is up to the implementation. It just so happens that many implementations have mutually chosen the same message of "is not a constructor".

Thanks bergus, aclaymore, and jmdyck. Very helpful. It's interesting that the Chrome and Node.js implementations (and surely others) interpret the TypeError as "is not a constructor". It suggests to me that (perhaps) the implementation teams do not view Symbol and BigInt as constructors. I would guess this is because Symbol and BigInt are the only standard built-in constructors that do not support the new operator. Just a guess. If so, though, it suggests that many implementation engineers define a constructor as a function object that supports the new operator. I have, too, up to this point. We know, of course, that the spec defines a constructor as a function object that supports [[Construct]]. But, I don't know an empirical way to test this. What do you think of the following (testable) definition: A constructor is a function object that possesses a prototype property. I'm thinking this is true in all cases. Thx much.

1 Like

Proxies can be used to check for the [[Construct]] slot, because they will only call the 'construct' hook if the target has that slot.

function isConstructor(f) {
    if (typeof f !== "function") return false;
    let hasConstructSlot = false;
    try {
      new (new Proxy(f, { construct() { hasConstructSlot = true; } }))();
    } catch {}
    return hasConstructSlot;
}
isConstructor(123); // false
isConstructor(() => {}); // false
isConstructor(({ method() {} }).method); // false
isConstructor(Set); // true
1 Like

Great. Thanks, aclaymore.

1 Like

Partially based on this discussion, I modified the definition of constructor in Diagrams of JavaScript constructors. I included the isConstructor function though I modified it slightly to make it easier to undertand for readers. I'd be glad to credit you, aclaymore, for this function in my article if you agree. The purpose of the article is to present a clean way to diagram constructors and their instances. Thanks again.

1 Like