I'm asking this question because I've noticed that TypeScript allows declaring constructors that return primitive types, e.g.:
type Constructor1 = new () => string; // Primitive string
as opposed to
type Constructor2 = new () => String; // String object
This made me wonder if JavaScript actually permits creating a function that returns a primitive value when invoked with new
semantics, i.e. a value that passes the primitiveness test:
function isPrimitive(value) {
return value !== Object(value);
}
Needless to say, I could not find any example of a constructor invocation that produces a primitive value, so I imagine this must be just another weirdness of the TypeScript type model. Or does a primitive constructor really exist?
For reference, this is what I have tried out.
The predefined constructors Number
, Boolean
, String
, etc. all produce an object when invoked with new
, although they return a primitive value when called as regular functions. i.e.
isPrimitive(new Number()) // false
isPrimitive(Number()) // true
function isPrimitive(value) {
return value !== Object(value);
}
console.log(isPrimitive(new Number()));
console.log(isPrimitive(Number()));
return
in constructorA return
statement overrides the instance of this
in the constructor, but only if the return value is an object:
const OBJECT = { foo: "bar" };
const PRIMITIVE = "baz";
function TheObject() {
return OBJECT;
}
function ThePrimitive() {
return PRIMITIVE;
}
console.log(isPrimitive(new TheObject())); // prints false
console.log(isPrimitive(new ThePrimitive())); // prints false
function isPrimitive(value) {
return value !== Object(value);
}
const OBJECT = { foo: "bar" };
const PRIMITIVE = "baz";
function TheObject() {
return OBJECT;
}
function ThePrimitive() {
return PRIMITIVE;
}
console.log(isPrimitive(new TheObject())); // prints false
console.log(isPrimitive(new ThePrimitive())); // prints false
construct
trapA proxy can provide a construct trap to handle invocations to a function with new
syntax. Whatever object the trap returns will be also returned by the constructor invocation. But, if a trap returns a primitive value other than undefined
, a TypeError
occurs.
const FooConstructor = new Proxy(
class { },
{ construct: () => 'foo' }
);
new FooConstructor(); // throws TypeError: proxy [[Construct]] must return an object
function isPrimitive(value) {
return value !== Object(value);
}
const FooConstructor = new Proxy(
class { },
{ construct: () => 'foo' }
);
new FooConstructor();
More ideas?
Basically if your constructor returns a primitive value, such as a string, number, boolean, null or undefined, (or you don't return anything which is equivalent to returning undefined ), a newly created object that inherits from the constructor's prototype will be returned.
No, constructor does not return any value. While declaring a constructor you will not have anything like return type. In general, Constructor is implicitly called at the time of instantiation. And it is not a method, its sole purpose is to initialize the instance variables.
In addition to primitive data types, methods (functions) can return the class objects.
You do not specify a return type for a constructor. A return statement in the body of a constructor cannot have a return value.
Can a constructor ever return a primitive?
The ECMAScript specification defines a constructor as:
...an object that supports the [[Construct]] internal method.
Although exotic objects have some liberty in implementing internal methods, the specification states at 6.1.7.3 Invariants of the Essential Internal Methods:
The Internal Methods of Objects of an ECMAScript engine must conform to the list of invariants specified below. Ordinary ECMAScript Objects as well as all standard exotic objects in this specification maintain these invariants. ECMAScript Proxy objects maintain these invariants by means of runtime checks on the result of traps invoked on the [[ProxyHandler]] object.
Any implementation provided exotic objects must also maintain these invariants for those objects. Violation of these invariants may cause ECMAScript code to have unpredictable behaviour and create security issues. However, violation of these invariants must never compromise the memory safety of an implementation.
An implementation must not allow these invariants to be circumvented in any manner such as by providing alternative interfaces that implement the functionality of the essential internal methods without enforcing their invariants.
[...]
The value returned by any internal method must be a Completion Record with either:
- [[Type]] =
normal
, [[Target]] =empty
, and [[Value]] = a value of the "normal return type" shown below for that internal method, or- [[Type]] =
throw
, [[Target]] =empty
, and [[Value]] = any ECMAScript language value.[...]
[[Construct]] ( )
- The normal return type is Object.
[...]
So in conclusion, a compliant ECMAScript implementation does not allow the return value of the [[Construct]]
internal method to be a primitive.
Take note that "normal return type" has a specific meaning here, which is also introduced in the quote above. "Normal" here refers to the case where no error was thrown.
You also phrased your question like this:
This made me wonder if JavaScript actually permits creating a function that returns a primitive value when invoked with new semantics
At 13.3.5 The new Operator, the specification stipulates that the Construct
procedure is executed (if all checks pass):
- Return ? Construct(constructor, argList).
And the procedure at 7.3.15 Construct ( F [ , argumentsList [ , newTarget ] ] ) in turn specifies:
- Return ? F.[[Construct]](argumentsList, newTarget).
So the new
operator will lead to the execution of the [[Construct]]
internal method, and so the above applies.
A constructor function can return
anything it wants, including primitives. It most often does not return
anything, resulting in the primitive undefined
.
However, any invocation of a constructor with new
will follow the well-established rules and always return an object. This is declared in the specification for the internal [[construct]] method, which has the signature (a List of any, Object) → Object
and is also specified with the clear invariant
[[Construct]]()
- The normal return type is Object.
- The target must also have a
[[Call]]
internal method.
This invariant applies to ordinary objects as well as to exotic ones, be they native or host-defined.
So yes, the TypeScript behaviour you described should be forbidden, a new
signature cannot result in a primitive value. new () => string
might as well be a compile-time error. Notice however that you won't be able to construct a value that is assignable to this type without resorting to any
typecasts, so you might as well consider it equivalent to never
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With