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/year/pending/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)。
    • 如果 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.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.is、Builtin.typeOf 和 Proxy.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'