Builtins.typeOf() and Builtins.is() ?
中文标题:Builtins.typeOf() 和 Builtins.is()
- 阶段: 未分阶段
- 状态: 已撤回
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
本提案引入了一个新的内置对象 Builtin,包含 typeOf() 和 is() 方法,以解决 instanceof 在跨 realm 场景中出现的类型检查问题。它添加了 \[\[Builtin\]\] 内部插槽和 @@builtin 符号来标记内置对象,从而实现跨 realm 的可靠类型识别和用户自定义的伪装。提案还包括 Proxy.isProxy() 方法。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
Builtin.is 和 Builtin.typeOf
动机
在许多情况下,使用 instanceof 进行现有类型检查可能会出现问题。例如:
在这个例子中,两个语句都返回有效的 Date 对象。然而,因为第二个是在不同的 realm 中创建的,它在当前 realm 中不被识别为 Date,尽管在其他所有方面都表现得正常。
在其他情况下,instanceof 无法提供足够的粒度,例如检查给定参数是无符号 16 位整数还是有符号 32 位整数。
本提案引入了一个新的 Builtin 内置对象,该对象公开了一些方法,允许对 ECMAScript 内置对象进行可靠的跨 realm 类型检查。
现有实践
Node.js 在一定程度上依赖此类检查来可靠地确定类型,以便在 util.format() 和 util.inspect() API 中进行调试、检查和显示格式化。此外,npm 上的 is 包(实现了类似的类型检查)目前每天下载量约为 3.3 万次以上。
Node.js 可以(并且已经)以宿主特定的方式在 Node.js API 中实现这些函数,但更希望这类类型检查成为语言 API 的常规部分。
例如:
需求
需要什么?
- 一种机制,用于可靠地确定任何给定对象是否为内置对象或内置对象的实例,即使在跨 realm 的情况下也是如此。
- 一种机制,用于可靠地确定不同 realm 中的对象是否对应于相同的内置对象(例如,一个 realm 中的
Date与另一个 realm 中的Date是同一个内置对象)。 - 避免引入新的或修改现有的语言语法。
- 允许宿主环境插入新的内置对象。
- 允许用户代码对象伪装成内置对象。
提议的 API
将对象标识为内置对象
对象通过以下方式被标识为内置对象:
- 一个新的
[[Builtin]]内部插槽,用于标记内置对象。 - 一个新的
@@builtin符号(Symbol.builtin)属性,其值是一个函数,默认行为是提供[[Builtin]]内部插槽的值。
[[Builtin]] 内部插槽
下表列出的内置对象具有 [[Builtin]] 内部插槽,其值为给定的字符串。未列出的内置对象 不 具有 [[Builtin]] 内部插槽。
注意:目前,像 %DatePrototype% 这样的内置原型对象有意 不 具有 [[Builtin]] 内部插槽。这样做的效果是,Builtin.typeOf(new Date()) 会返回 'Date',而 Builtin.typeOf(Object.getPrototypeOf(new Date())) 会返回 'object',尽管 %DatePrototype% 是内置对象。这样做的理由是尚不清楚内置原型对象 是否 需要被识别为内置对象。
此外,所有内置的非构造函数和方法的 [[Builtin]] 内部插槽的值等于函数名称。这些用于允许使用 Builtin.is() 来确定两个函数/方法实例是否表示相同的内置函数或方法。
例如,
Symbol.builtin
所有具有 [[Builtin]] 内部插槽的内置对象的 @@builtin 自有属性的初始值是同一个函数,该函数返回 [[Builtin]] 内部插槽的值。没有 [[Builtin]] 内部插槽的内置对象没有 @@builtin 自有属性的初始值。
对象如果具有 @@builtin 自有属性,则可被检测为内置对象。
对象如果其构造函数具有 @@builtin 自有或继承属性,则可被检测为内置对象的 实例。
将 @@builtin 属性设置为非函数值会使该对象或其实例不再被检测为内置对象:
@@builtin 属性具有以下特性:
[[Configurable]]: true[[Enumerable]]: false[[Writable]]: true
抽象操作
GetBuiltinValue
抽象操作 GetBuiltinValue 接收参数 object,执行以下步骤:
- 令
fn为? GetMethod(object, @@builtin)。 - 如果
fn为undefined,返回undefined。 - 令
value为? Call(fn, object)。 - 如果
value为undefined,返回undefined。 - 返回
? ToString(value)。
GetOwnBuiltinValue
抽象操作 GetOwnBuiltinValue 接收参数 object,执行以下步骤:
- 令
hasProperty为? HasOwnProperty(object, @@builtin)。 - 如果
hasProperty为false,返回undefined。 - 返回
? GetBuiltinValue(object)。
Builtin
Builtin 对象是 %Builtin% 内置对象,也是 global 对象的 Builtin 属性的初始值。Builtin 对象是一个普通对象。
Builtin 对象的 [[Prototype]] 内部插槽的值是内置对象 %ObjectPrototype%。
Builtin 对象不是函数对象。它没有 [[Construct]] 内部方法;不能使用 new 运算符将 Builtin 对象用作构造函数。Builtin 对象也没有 [[Call]] 内部方法;不能作为函数调用 Builtin 对象。
Builtin.is(value1, value2)
当使用参数 value1 和 value2 调用时:
- 如果
Type(value1)不是Object,返回false。 - 令
V1为? GetOwnBuiltinValue(value1)。 - 如果
V1为undefined,返回false。 - 如果
value2为undefined,返回false。 - 如果
Type(value2)不是Object,返回false。 - 令
V2为? GetOwnBuiltinValue(value2)。 - 令
same为执行严格相等比较V1 === V2的结果。 - 返回
same
如果给定的两个值都具有 @@builtin 自有属性函数,并且每个函数返回的值在强制转换为字符串后严格相等,则 Builtin.is() 函数返回 true。否则,返回 false。
请注意,用户代码可以修改任何对象上的 @@builtin 自有属性:
默认情况下,Builtin.is() 函数不会抛出异常。如果用户提供的 @@builtin 函数抛出异常或返回无法强制转换为字符串的值(例如 Symbol 值),Builtin.is() 可能会抛出异常。
Builtin.typeOf(arg)
当使用参数 arg 调用 typeOf() 函数时:
- 如果
Type(arg)是Object,则:- 令
C为? Get(arg, "constructor")。 - 如果
C不是undefined,则:- 令
V为? GetBuiltinValue(C)。 - 如果
V不是undefined,返回V。
- 令
- 令
- 返回
typeof arg。
例如:
默认情况下,Builtin.typeOf() 函数不会抛出异常。如果用户提供的 @@builtin 函数抛出异常或返回无法强制转换为字符串的值(例如 Symbol 值),Builtin.typeOf() 可能会抛出异常。
注意:由于 Proxy 实例的特性,Builtin.typeOf(proxyObj) 永远不会返回 'Proxy'。
Proxy.isProxy(value)
如果 value 是 Proxy 异质对象,则返回 true,否则返回 false。
Proxy.isProxy() 函数不会抛出异常。
注意:由于 Proxy 的安全问题,宿主环境应允许提供选项,强制 Proxy.isProxy(value) 始终返回 false。例如,Node.js 可以提供一个如 --disable-isproxy 的命令行参数。
备注
-
可以通过将函数添加到现有的内置对象来避免添加新的
%Builtin%内置对象,例如Object.isBuiltin()或Object.typeOf()。 -
使用
@@builtin意味着任何对象都可以通过将@@builtin自有属性设置为任何所需的值来撒谎说自己是内置对象。这是设计使然。例如,polyfill/shim 和安全 realm 代码必须能够创建内置对象、删除或替换不合规的内置对象——因此,在运行其他代码之前运行的 shim 必须能够创建自己的内置替换,并真正伪装成原始的内置对象。 -
为什么要有单独的
Proxy.isProxy()函数?原因很简单,Proxy对象的行为不像其他任何东西。使用Proxy.isProxy()的理由是,在调试时,通常有必要知道感兴趣的对象是否是 Proxy。 -
global对象上的Builtin属性初始设置为Builtin对象。此属性具有以下特性:[[Configurable]]: true[[Enumerable]]: true[[Writable]]: true
-
Builtin.is、Builtin.typeOf和Proxy.isProxy属性具有以下特性:[[Configurable]]: true[[Enumerable]]: true[[Writable]]: true
示例
由于 @@builtin 的值是一个函数,原始实现可以被捕获、缓存并在以后恢复:
注意:初始的 @@builtin 函数的行为是,如果 this 对象存在 [[Builtin]] 内部插槽,则返回该插槽的值。因此,可以获取该函数的引用一次,并在多个对象上使用它: