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

瀏覽器相容性

另見