Symbol.unscopables

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

试一试

众所周知的符号 Symbol.unscopables

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

描述

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

[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");
}

包含 with (values) 的代码在添加 Array.prototype.values() 时导致某些网站在 Firefox 中出现故障 (Firefox Bug 883914)。此外,这意味着如果任何未来的数组方法添加会隐式更改 with 作用域,则可能会出现问题。因此,引入了 [Symbol.unscopables] 符号并在 Array 上实现为 Array.prototype[Symbol.unscopables] 以防止某些 Array 方法作用域到 with 语句中。

对象中的 Unscopables

您还可以为自己的对象设置 [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
}

避免使用非空原型对象作为 [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 语言规范
# sec-symbol.unscopables

浏览器兼容性

BCD 表格仅在启用 JavaScript 的浏览器中加载。

另请参阅