Symbol.unscopables

Symbol.unscopables 静态数据属性表示 知名 Symbol Symbol.unscopableswith 语句会在作用域对象上查找此 Symbol,以获取包含一组不应在 with 环境中成为绑定的属性的集合。

试一试

const object = {
  foo: 42,
};

object[Symbol.unscopables] = {
  foo: true,
};

with (object) {
  console.log(foo);
  // Expected output: Error: foo is not defined
}

知名 Symbol Symbol.unscopables

Symbol.unscopables 的属性特性
可写
可枚举
可配置

描述

可以通过 Symbol.unscopables 访问的 [Symbol.unscopables] Symbol 可以定义在任何对象上,以排除属性名称作为 with 环境绑定中的词法变量被公开。请注意,在使用 严格模式 时,with 语句不可用,并且此 Symbol 可能不需要。

[Symbol.unscopables] 对象的属性设置为 true(或任何 真值)将使 with 作用域对象的相应属性成为不可作用域,因此不会引入到 with 主体作用域中。将属性设置为 false(或任何 假值)将使其可作用域,从而作为词法作用域变量出现。

在决定 x 是否不可作用域时,会查找 [Symbol.unscopables] 属性的整个原型链,以查找名为 x 的属性。这意味着,如果您将 [Symbol.unscopables] 声明为一个普通对象,Object.prototype 的属性(如 toString)也将变得不可作用域,这可能会导致向后不兼容,因为旧代码可能假设这些属性通常是作用域内的(请参阅 下面的示例)。建议使您的自定义 [Symbol.unscopables] 属性的原型为 null,就像 Array.prototype[Symbol.unscopables] 所做的那样。

DOM API(如 Element.prototype.append())也使用此协议。

示例

with 语句中的作用域

以下代码在 ES5 及更早版本中运行正常。但是在 ECMAScript 2015 中,引入了 Array.prototype.values() 方法。这意味着在 with 环境中,“values”现在将是 Array.prototype.values() 方法,而不是 with 语句外的变量。

js
var values = [];

with (values) {
  // If [Symbol.unscopables] did not exist, values would become
  // Array.prototype.values starting with ECMAScript 2015.
  // And an error would have occurred.
  values.push("something");
}

Array.prototype.values() 被添加时,包含 with (values) 的代码导致 Firefox 中的一些网站出现故障(Firefox Bug 883914)。此外,这暗示任何未来的数组方法添加都可能具有破坏性,因为它可能隐式更改 with 作用域。因此,引入了 [Symbol.unscopables] Symbol,并将其在 Array 上实现为 Array.prototype[Symbol.unscopables],以防止某些 Array 方法被作用域到 with 语句中。

对象中的不可作用域

您也可以为自己的对象设置 [Symbol.unscopables]

js
const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

obj[Symbol.unscopables] = {
  // Make the object have `null` prototype to prevent
  // `Object.prototype` methods from being unscopable
  __proto__: null,
  // `foo` will be scopable
  foo: false,
  // `bar` will be unscopable
  bar: true,
  // `baz` is omitted; because `undefined` is falsy, it is also scopable (default)
};

with (obj) {
  console.log(foo); // 1
  console.log(bar); // ReferenceError: bar is not defined
  console.log(baz); // 3
}

避免使用非 null 原型对象作为 [Symbol.unscopables]

[Symbol.unscopables] 声明为普通对象而不消除其原型可能会导致细微的错误。请考虑以下在 [Symbol.unscopables] 之前正常工作的代码。

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
};

with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "Use with statements, you must not"
}

为了保持向后兼容性,您决定在向 character 添加更多属性时添加一个 [Symbol.unscopables] 属性。您可能天真地这样做:

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // Make `student` unscopable
    student: true,
  },
};

但是,上面的代码现在会中断。

js
with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "[object Undefined]"
}

这是因为在查找 character[Symbol.unscopables].toString 时,它会返回 Object.prototype.toString(),这是一个真值,因此使 with() 语句中的 toString() 调用指向 globalThis.toString() —— 因为它没有 this 调用,所以 thisundefined,导致它返回 [object Undefined]

即使方法没有被 character 覆盖,将其设为不可作用域也会改变 this 的值。

js
const proto = {};
const obj = { __proto__: proto };

with (proto) {
  console.log(isPrototypeOf(obj)); // true; `isPrototypeOf` is scoped and `this` is `proto`
}

proto[Symbol.unscopables] = {};

with (proto) {
  console.log(isPrototypeOf(obj)); // TypeError: Cannot convert undefined or null to object
  // `isPrototypeOf` is unscoped and `this` is undefined
}

为了解决这个问题,请始终确保 [Symbol.unscopables] 只包含您希望不可作用域的属性,而不包含 Object.prototype 属性。

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // Make the object have `null` prototype to prevent
    // `Object.prototype` methods from being unscopable
    __proto__: null,
    // Make `student` unscopable
    student: true,
  },
};

规范

规范
ECMAScript® 2026 语言规范
# sec-symbol.unscopables

浏览器兼容性

另见