For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/proposal-istypes.md.
  • 简体中文
  • Builtins.typeOf() and Builtins.is() ?

    中文标题:Builtins.typeOf() 和 Builtins.is()

    提案概览
    提案速览

    本提案引入了一个新的内置对象 Builtin,包含 typeOf()is() 方法,以解决 instanceof 在跨 realm 场景中出现的类型检查问题。它添加了 \[\[Builtin\]\] 内部插槽和 @@builtin 符号来标记内置对象,从而实现跨 realm 的可靠类型识别和用户自定义的伪装。提案还包括 Proxy.isProxy() 方法。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    Builtin.is 和 Builtin.typeOf

    动机

    在许多情况下,使用 instanceof 进行现有类型检查可能会出现问题。例如:

    $ ./node
    > (new Date()) instanceof Date
    true
    > (vm.runInNewContext('new Date()')) instanceof Date
    false

    在这个例子中,两个语句都返回有效的 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 的常规部分。

    例如:

    $ ./node
    > util.isDate(new Date())
    true
    > util.isDate(vm.runInNewContext('new Date()'))
    true
    > vm.runInNewContext('new Date()') instanceof Date
    false

    需求

    需要什么?

    • 一种机制,用于可靠地确定任何给定对象是否为内置对象或内置对象的实例,即使在跨 realm 的情况下也是如此。
    • 一种机制,用于可靠地确定不同 realm 中的对象是否对应于相同的内置对象(例如,一个 realm 中的 Date 与另一个 realm 中的 Date 是同一个内置对象)。
    • 避免引入新的或修改现有的语言语法。
    • 允许宿主环境插入新的内置对象。
    • 允许用户代码对象伪装成内置对象。

    提议的 API

    将对象标识为内置对象

    对象通过以下方式被标识为内置对象:

    • 一个新的 [[Builtin]] 内部插槽,用于标记内置对象。
    • 一个新的 @@builtin 符号(Symbol.builtin)属性,其值是一个函数,默认行为是提供 [[Builtin]] 内部插槽的值。
    [[Builtin]] 内部插槽

    下表列出的内置对象具有 [[Builtin]] 内部插槽,其值为给定的字符串。未列出的内置对象 具有 [[Builtin]] 内部插槽。

    内置名称内置名称
    %Array%'Array'
    %ArrayBuffer%'ArrayBuffer'
    %AsyncFunction%'AsyncFunction'
    %Atomics%'Atomics'
    %Boolean%'Boolean'
    %DataView%'DataView'
    %Date%'Date'
    %Error%'Error'
    %EvalError%'EvalError'
    %Float32Array%'Float32Array'
    %Float64Array%'Float64Array'
    %Function%'function'
    %GeneratorFunction%'GeneratorFunction'
    %Int8Array%'Int8Array'
    %Int16Array%'Int16Array'
    %Int32Array%'Int32Array'
    %JSON%'JSON'
    %Map%'Map'
    %Math%'Math'
    %Number%'Number'
    %Object%'object'
    %Promise%'Promise'
    %Proxy%'Proxy'
    %RangeError%'RangeError'
    %ReferenceError%'ReferenceError'
    %Reflect%'Reflect'
    %RegExp%'RegExp'
    %Set%'Set'
    %SharedArrayBuffer%'SharedArrayBuffer'
    %String%'String'
    %Symbol%'symbol'
    %SyntaxError%'SyntaxError'
    %TypeError%'TypeError'
    %Uint8Array%'Uint8Array'
    %Uint8ClampedArray%'Uint8ClampedArray'
    %Uint16Array%'Uint16Array'
    %Uint32Array%'Uint32Array'
    %URIError%'URIError'
    %WeakMap%'WeakMap'
    %WeakSet%'WeakSet'

    注意:目前,像 %DatePrototype% 这样的内置原型对象有意 具有 [[Builtin]] 内部插槽。这样做的效果是,Builtin.typeOf(new Date()) 会返回 'Date',而 Builtin.typeOf(Object.getPrototypeOf(new Date())) 会返回 'object',尽管 %DatePrototype% 是内置对象。这样做的理由是尚不清楚内置原型对象 是否 需要被识别为内置对象。

    此外,所有内置的非构造函数和方法的 [[Builtin]] 内部插槽的值等于函数名称。这些用于允许使用 Builtin.is() 来确定两个函数/方法实例是否表示相同的内置函数或方法。

    例如,

    Builtin.is(eval, vm.runInNewContext('eval'));                // true
    Builtin.is(Object.prototype.toString,
               vm.runInNewContext('Object.prototype.toString')); // true
    Symbol.builtin

    所有具有 [[Builtin]] 内部插槽的内置对象的 @@builtin 自有属性的初始值是同一个函数,该函数返回 [[Builtin]] 内部插槽的值。没有 [[Builtin]] 内部插槽的内置对象没有 @@builtin 自有属性的初始值。

    const builtIn1 = Date[Symbol.builtin];
    const builtIn2 = Uint8Array[Symbol.builtin];
    const same = builtIn1 === builtIn2;         // true

    对象如果具有 @@builtin 自有属性,则可被检测为内置对象。

    对象如果其构造函数具有 @@builtin 自有或继承属性,则可被检测为内置对象的 实例

    class Foo {
      static [Symbol.builtin]() {
        return 'Foo';
      }
    }
    class Bar extends Foo {}
    
    Builtin.typeOf(new Foo());     // 'Foo'
    
    Builtin.typeOf(new Bar());     // 'Foo'

    @@builtin 属性设置为非函数值会使该对象或其实例不再被检测为内置对象:

    Builtin.typeOf(new Uint8Array(0));      // 'Uint8Array'
    
    Uint8Array[Symbol.builtin] = undefined;
    
    Builtin.typeOf(new Uint8Array(0));      // 'object'

    @@builtin 属性具有以下特性:

    • [[Configurable]]: true
    • [[Enumerable]]: false
    • [[Writable]]: true

    抽象操作

    GetBuiltinValue

    抽象操作 GetBuiltinValue 接收参数 object,执行以下步骤:

    • fn? GetMethod(object, @@builtin)
    • 如果 fnundefined,返回 undefined
    • value? Call(fn, object)
    • 如果 valueundefined,返回 undefined
    • 返回 ? ToString(value)
    GetOwnBuiltinValue

    抽象操作 GetOwnBuiltinValue 接收参数 object,执行以下步骤:

    • hasProperty? HasOwnProperty(object, @@builtin)
    • 如果 hasPropertyfalse,返回 undefined
    • 返回 ? GetBuiltinValue(object)

    Builtin

    Builtin 对象是 %Builtin% 内置对象,也是 global 对象的 Builtin 属性的初始值。Builtin 对象是一个普通对象。

    Builtin 对象的 [[Prototype]] 内部插槽的值是内置对象 %ObjectPrototype%

    Builtin 对象不是函数对象。它没有 [[Construct]] 内部方法;不能使用 new 运算符将 Builtin 对象用作构造函数。Builtin 对象也没有 [[Call]] 内部方法;不能作为函数调用 Builtin 对象。

    Builtin.is(value1, value2)

    当使用参数 value1value2 调用时:

    • 如果 Type(value1) 不是 Object,返回 false
    • V1? GetOwnBuiltinValue(value1)
    • 如果 V1undefined,返回 false
    • 如果 value2undefined,返回 false
    • 如果 Type(value2) 不是 Object,返回 false
    • V2? GetOwnBuiltinValue(value2)
    • same 为执行严格相等比较 V1 === V2 的结果。
    • 返回 same

    如果给定的两个值都具有 @@builtin 自有属性函数,并且每个函数返回的值在强制转换为字符串后严格相等,则 Builtin.is() 函数返回 true。否则,返回 false

    Builtin.is(Date, vm.runInNewContext('Date'));     // true
    Builtin.is(Date, vm.runInNewContext('Number'));   // false
    Builtin.is(Date, vm.runInNewContext('{}'));       // false
    Builtin.is({}, vm.runInNewContext('{}'));         // false
    
    Date = {};
    Builtin.is(Date, vm.runInNewContext('Date'));     // false

    请注意,用户代码可以修改任何对象上的 @@builtin 自有属性:

    Date[Symbol.builtin] = undefined;
    Builtin.is(Date, vm.runInNewContext('Date'));     // false

    默认情况下,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([]);                             // 'Array'
    Builtin.typeOf(new ArrayBuffer());              // 'ArrayBuffer'
    Builtin.typeOf(async function foo() {});        // 'AsyncFunction'
    Builtin.typeOf(new Boolean());                  // 'Boolean'
    Builtin.typeOf(new DataView(buffer));           // 'DataView'
    Builtin.typeOf(new Date());                     // 'Date'
    Builtin.typeOf(new Error());                    // 'Error'
    Builtin.typeOf(new EvalError());                // 'EvalError'
    Builtin.typeOf(new Float32Array());             // 'Float32Array'
    Builtin.typeOf(new Float64Array());             // 'Float64Array'
    Builtin.typeOf(function() {});                  // 'function'
    Builtin.typeOf(function*() {});                 // 'GeneratorFunction'
    Builtin.typeOf(new Int16Array());               // 'Int16Array'
    Builtin.typeOf(new Int32Array());               // 'Int32Array'
    Builtin.typeOf(new Int8Array());                // 'Int8Array'
    Builtin.typeOf(new InternalError());            // 'InternalError'
    Builtin.typeOf(new Intl.Collator());            // 'Collator'
    Builtin.typeOf(new Intl.DateTimeFormat());      // 'DateTimeFormat'
    Builtin.typeOf(new Intl.NumberFormat());        // 'NumberFormat'
    Builtin.typeOf(new Map());                      // 'Map'
    Builtin.typeOf(new Number());                   // 'Number'
    Builtin.typeOf(new Object());                   // 'object'
    Builtin.typeOf(new Promise(() => {}));          // 'Promise'
    Builtin.typeOf(new RangeError());               // 'RangeError'
    Builtin.typeOf(new ReferenceError());           // 'ReferenceError'
    Builtin.typeOf(new RegExp(''));                 // 'RegExp'
    Builtin.typeOf(new Set());                      // 'Set'
    Builtin.typeOf(new SharedArrayBuffer());        // 'SharedArrayBuffer'
    Builtin.typeOf(new String());                   // 'String'
    Builtin.typeOf(new SyntaxError());              // 'SyntaxError'
    Builtin.typeOf(new TypeError());                // 'TypeError'
    Builtin.typeOf(new URIError());                 // 'URIError'
    Builtin.typeOf(new Uint16Array());              // 'Uint16Array'
    Builtin.typeOf(new Uint32Array());              // 'Uint32Array'
    Builtin.typeOf(new Uint8Array());               // 'Uint8Array'
    Builtin.typeOf(new Uint8ClampedArray());        // 'Uint8ClampedArray'
    Builtin.typeOf(new WeakMap());                  // 'WeakMap'
    Builtin.typeOf(new WeakSet());                  // 'WeatSet'
    Builtin.typeOf(new WebAssembly.Module());       // 'Module'
    Builtin.typeOf(new WebAssembly.Instance());     // 'Instance'
    Builtin.typeOf(new WebAssembly.Memory());       // 'Memory'
    Builtin.typeOf(new WebAssembly.Table());        // 'Table'
    Builtin.typeOf(new WebAssembly.CompileError()); // 'CompileError'
    Builtin.typeOf(new WebAssembly.LinkError());    // 'LinkError'
    Builtin.typeOf(new WebAssembly.RuntimeError()); // 'RuntimeError'
    Builtin.typeOf(null);                           // 'null'
    Builtin.typeOf(undefined);                      // 'undefined'
    Builtin.typeOf({});                             // 'object'
    Builtin.typeOf(true);                           // 'boolean'
    Builtin.typeOf(1);                              // 'number'
    Builtin.typeOf('test');                         // 'string'
    Builtin.typeOf(Symbol('foo'));                  // 'symbol'
    Builtin.typeOf(function() {});                  // 'function'
    
    
    class MyArray extends Uint8Array {}
    const myArray = new MyArray();
    Builtin.typeOf(myArray);                        // 'Uint8Array'
    
    vm.runInNewContext('Builtin.typeOf(myArray)', { myArray }); // 'Uint8Array'

    默认情况下,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.isBuiltin.typeOfProxy.isProxy 属性具有以下特性:

      • [[Configurable]]: true
      • [[Enumerable]]: true
      • [[Writable]]: true

    示例

    function formatValue(value) {
      switch (Builtin.typeOf(value)) {
        case 'Date':
          return formatDate(value);
        case 'Array':
          return formatArray(value);
        case 'RegExp':
          return formatRegExp(value);
        /** ... **/
      }
    }
    const val = vm.runInNewContext('Date');
    if (Builtin.is(val, Date)) {
      /** ... **/
    } else if (Builtin.is(val, Math)) {
      /** ... **/
    }

    由于 @@builtin 的值是一个函数,原始实现可以被捕获、缓存并在以后恢复:

    const origDateBuiltin = Date[Symbol.builtin];
    Date[Symbol.builtin] = undefined;
    
    Builtin.is(Date, vm.runInNewContext('Date'));  // false
    Builtin.typeOf(Date);                          // 'object'
    
    origDateBuiltin.call(Date);                    // 'Date'
    
    Date[Symbol.builtin] = origDateBuiltin;
    
    Builtin.is(Date, vm.runInNewContext('Date'));  // true
    Builtin.typeOf(Date);                          // 'Date'

    注意:初始的 @@builtin 函数的行为是,如果 this 对象存在 [[Builtin]] 内部插槽,则返回该插槽的值。因此,可以获取该函数的引用一次,并在多个对象上使用它:

    const origBuiltin = Date[Symbol.builtin];
    Uint8Array[Symbol.builtin] = origBuiltin;
    
    class Foo {}
    Foo[Symbol.builtin] = origBuiltin;
    
    Date[Symbol.builtin]();                                  // 'Date'
    Uint8Array[Symbol.builtin]();                            // 'Uint8Array'
    Foo[Symbol.builtin]();                                   // undefined
    
    Builtin.is(Date, vm.runInNewContext('Date'));            // true
    Builtin.is(Uint8Array, vm.runInNewContext('Uin8Array')); // true
    
    Builtin.typeOf(new Date());                              // 'Date'
    Builtin.typeOf(new Uint8Array());                        // 'Uint8Array'
    Builtin.typeOf(new Foo());                               // 'object'