构造函数可以 return 原语吗?

Can a constructor ever return a primitive?

我问这个问题是因为我注意到 TypeScript 允许声明 return 原始类型的构造函数,例如:

type Constructor1 = new () => string; // Primitive string

相对于

type Constructor2 = new () => String; // String object

这让我想知道 JavaScript 是否真的允许创建一个函数,当使用 new 语义调用时,return 是一个原始值,即传递 的值:

function isPrimitive(value) {
    return value !== Object(value);
}

不用说,我找不到任何生成原始值的构造函数调用的示例,所以我想这一定是 TypeScript 类型模型的另一个怪异之处。或者原始构造函数真的存在吗?


作为参考,这是我试过的。

预定义的构造函数

预定义的构造函数 NumberBooleanString 等在使用 new 调用时都会生成一个对象,尽管它们 return 是一个原语作为常规函数调用时的值。即

isPrimitive(new Number()) // false

isPrimitive(Number())     // true

function isPrimitive(value) {
    return value !== Object(value);
}

console.log(isPrimitive(new Number()));
console.log(isPrimitive(Number()));

return 在构造函数中

A return 语句在构造函数中覆盖 this 的实例,但前提是 return 值是一个对象:

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陷阱

代理可以提供 construct trap 来处理对具有 new 语法的函数的调用。陷阱 returns 的任何对象也将被构造函数调用 returned。但是,如果陷阱 return 是 undefined 以外的原始值,则会发生 TypeError

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();


更多想法?

Can a constructor ever return a primitive?

ECMAScript 规范将 constructor 定义为:

...an object that supports the [[Construct]] internal method.

虽然 exotic 对象在实现内部方法方面有一定的自由度,但规范在 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.

[...]

所以总而言之,兼容 ECMAScript 实现不允许 [[Construct]] 内部方法的 return 值是原始值。

注意这里的“normal return type”是有特定含义的,在上面的引用中也有介绍。这里的“正常”是指没有报错的情况。


您还这样表述了您的问题:

This made me wonder if JavaScript actually permits creating a function that returns a primitive value when invoked with new semantics

13.3.5 The new Operator,规范规定执行Construct过程(如果所有检查都通过):

  1. Return ? Construct(constructor, argList).

7.3.15 Construct ( F [ , argumentsList [ , newTarget ] ] ) 处的过程又指定:

  1. Return ? F.[[Construct]](argumentsList, newTarget).

因此 new 运算符将导致 [[Construct]] 内部方法的执行,因此上述内容适用。

构造函数可以 return 它想要的任何东西,包括原语。它通常 而不是 return 任何事情,导致原始 undefined.

但是,使用 new 对构造函数的任何调用都将遵循 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.

此不变式适用于普通对象和奇异对象,无论是本地对象还是主机定义的对象。

所以是的,您描述的 TypeScript 行为应该被禁止,new 签名不能产生原始值。 new () => string 也可能是编译时错误。但是请注意,如果不求助于 any 类型转换,您将无法构造可分配给此类型的值,因此您不妨将其视为等同于 never.