符號
Baseline 廣泛可用 *
Symbol 是一個內建物件,其建構函式返回一個 symbol 原始值——也稱為 Symbol 值或簡稱 Symbol——它保證是獨一無二的。Symbol 通常用於向物件新增唯一的屬性鍵,這些鍵不會與其他程式碼可能新增到物件的鍵衝突,並且對其他程式碼通常用於訪問物件的任何機制都是隱藏的。這使得一種弱形式的封裝,或者一種弱形式的資訊隱藏成為可能。
每次 Symbol() 呼叫都保證返回一個唯一的 Symbol。每次 Symbol.for("key") 呼叫都會針對給定 "key" 值始終返回相同的 Symbol。當呼叫 Symbol.for("key") 時,如果在全域性 Symbol 登錄檔中找到具有給定鍵的 Symbol,則返回該 Symbol。否則,將建立一個新的 Symbol,以給定鍵新增到全域性 Symbol 登錄檔,然後返回。
描述
要建立一個新的原始 Symbol,你可以使用 Symbol(),並可選地傳入一個字串作為其描述。
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");
上述程式碼建立了三個新的 Symbol。注意,Symbol("foo") 不會將字串 "foo" 強制轉換為 Symbol。它每次都會建立一個新的 Symbol。
Symbol("foo") === Symbol("foo"); // false
const sym = new Symbol(); // TypeError
這可以防止開發者建立顯式的 Symbol 包裝物件而不是新的 Symbol 值,這可能會讓人感到驚訝,因為通常可以為原始資料型別建立顯式的包裝物件(例如,new Boolean、new String 和 new Number)。
如果你確實想建立一個 Symbol 包裝物件,你可以使用 Object() 函式。
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"
因為 Symbol 是唯一具有引用標識(即,你不能兩次建立相同的 Symbol)的原始資料型別,所以它們在某些方面表現得像物件。例如,它們是可以被垃圾回收的,因此可以儲存在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 物件中。
全域性 Symbol 登錄檔中的共享 Symbol
上述使用 Symbol() 函式的語法將建立一個 Symbol,其值在程式生命週期內保持唯一。要建立跨檔案甚至跨領域(每個領域都有自己的全域性作用域)可用的 Symbol,請使用 Symbol.for() 和 Symbol.keyFor() 方法從全域性 Symbol 登錄檔中設定和檢索 Symbol。
請注意,“全域性 Symbol 登錄檔”只是一個虛擬概念,可能不對應於 JavaScript 引擎中的任何內部資料結構——即使存在這樣的登錄檔,其內容也無法透過 JavaScript 程式碼訪問,除了透過 for() 和 keyFor() 方法。
方法 Symbol.for(tokenString) 接受一個字串鍵並從登錄檔中返回一個 Symbol 值,而 Symbol.keyFor(symbolValue) 接受一個 Symbol 值並返回與之對應的字串鍵。兩者互為逆運算,因此以下結果為 true
Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true
由於註冊 Symbol 可以隨意建立,它們幾乎完全像它們包裝的字串一樣。因此,它們不保證是唯一的,並且不能被垃圾回收。因此,註冊 Symbol 不允許在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 物件中使用。
知名 Symbol
Symbol 建構函式的所有靜態屬性本身都是 Symbol,它們的值在不同領域是常量。它們被稱為 知名 Symbol,其目的是充當某些內建 JavaScript 操作的“協議”,允許使用者自定義語言的行為。例如,如果一個建構函式有一個以 Symbol.hasInstance 為名稱的方法,這個方法將使用 instanceof 運算子來編碼其行為。
在知名 Symbol 之前,JavaScript 使用普通屬性來實現某些內建操作。例如,JSON.stringify 函式會嘗試呼叫每個物件的 toJSON() 方法,String 函式會呼叫物件的 toString() 和 valueOf() 方法。然而,隨著語言中添加了更多的操作,將每個操作指定為“魔術屬性”可能會破壞向後相容性,並使語言行為更難理解。知名 Symbol 允許自定義對普通程式碼“不可見”,這些程式碼通常只讀取字串屬性。
注意:規範過去使用 @@<symbol-name> 符號來表示知名 Symbol。例如,Symbol.hasInstance 寫成 @@hasInstance,而 Array.prototype[Symbol.iterator]() 方法則被稱為 Array.prototype[@@iterator]()。這種符號在規範中不再使用,但你可能仍然在舊文件或討論中看到它。
知名 Symbol 沒有垃圾回收的概念,因為它們以固定集合存在,並且在程式的整個生命週期內都是唯一的,類似於 Array.prototype 等固有物件,因此它們也允許在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 物件中使用。
在物件上查詢 Symbol 屬性
方法 Object.getOwnPropertySymbols() 返回一個 Symbol 陣列,允許你在給定物件上查詢 Symbol 屬性。請注意,每個物件在初始化時都沒有自己的 Symbol 屬性,因此除非你已在該物件上設定了 Symbol 屬性,否則此陣列將為空。
建構函式
Symbol()-
返回 Symbol 型別的原始值。使用
new呼叫時會丟擲錯誤。
靜態屬性
靜態屬性都是知名 Symbol。在這些 Symbol 的描述中,我們將使用“Symbol.hasInstance 是一個方法,用於確定……”之類的語言,但請記住,這指的是一個物件的方法以這個 Symbol 作為方法名稱的語義(因為知名 Symbol 充當“協議”),而不是描述 Symbol 值本身。
Symbol.asyncDispose-
當物件超出作用域時,非同步釋放物件資源的方法。由
await using宣告使用。 Symbol.asyncIterator-
返回物件預設 AsyncIterator 的方法。由
for await...of使用。 Symbol.dispose-
當物件超出作用域時,釋放物件資源的方法。由
using宣告使用。 Symbol.hasInstance-
一個確定建構函式物件是否將其識別為其例項的方法。由
instanceof使用。 Symbol.isConcatSpreadable-
一個布林值,指示物件是否應被展平為其陣列元素。由
Array.prototype.concat()使用。 Symbol.iterator-
返回物件預設迭代器的方法。由
for...of使用。 Symbol.match-
與字串匹配的方法,也用於確定物件是否可用作正則表示式。由
String.prototype.match()使用。 Symbol.matchAll-
返回一個迭代器的方法,該迭代器生成正則表示式與字串的匹配項。由
String.prototype.matchAll()使用。 Symbol.replace-
替換字串中匹配的子字串的方法。由
String.prototype.replace()使用。 Symbol.search-
返回字串中與正則表示式匹配的索引的方法。由
String.prototype.search()使用。 Symbol.species-
用於建立派生物件的建構函式。
Symbol.split-
在與正則表示式匹配的索引處分割字串的方法。由
String.prototype.split()使用。 Symbol.toPrimitive-
將物件轉換為原始值的方法。
Symbol.toStringTag-
用於物件預設描述的字串值。由
Object.prototype.toString()使用。 Symbol.unscopables-
一個物件值,其自身和繼承的屬性名稱被排除在關聯物件的
with環境繫結之外。
靜態方法
Symbol.for()-
在全域性 Symbol 登錄檔中搜索具有給定
key的現有註冊 Symbol,如果找到則返回。否則,將建立一個新的 Symbol 並以key註冊。 Symbol.keyFor()-
從全域性 Symbol 登錄檔中檢索給定 Symbol 的共享 Symbol 鍵。
例項屬性
這些屬性定義在 Symbol.prototype 上,並由所有 Symbol 例項共享。
Symbol.prototype.constructor-
建立例項物件的建構函式。對於
Symbol例項,初始值是Symbol建構函式。 Symbol.prototype.description-
一個只讀字串,包含 Symbol 的描述。
Symbol.prototype[Symbol.toStringTag]-
[Symbol.toStringTag]屬性的初始值是字串"Symbol"。此屬性在Object.prototype.toString()中使用。然而,由於Symbol也有其自己的toString()方法,除非你以 Symbol 作為thisArg呼叫Object.prototype.toString.call(),否則此屬性不會被使用。
例項方法
Symbol.prototype.toString()-
返回一個包含 Symbol 描述的字串。覆蓋
Object.prototype.toString()方法。 Symbol.prototype.valueOf()-
返回 Symbol。覆蓋
Object.prototype.valueOf()方法。 Symbol.prototype[Symbol.toPrimitive]()-
返回 Symbol。
示例
將 typeof 運算子與 Symbol 一起使用
typeof 運算子可以幫助你識別 Symbol。
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";
Symbol 型別轉換
在使用 Symbol 型別轉換時需要注意的一些事項。
- 當嘗試將 Symbol 轉換為數字時,會丟擲
TypeError(例如,+sym或sym | 0)。 - 使用鬆散相等時,
Object(sym) == sym返回true。 Symbol("foo") + "bar"會丟擲TypeError(無法將 Symbol 轉換為字串)。這可以防止你悄悄地從 Symbol 建立新的字串屬性名,例如。- “更安全”的
String(sym)轉換 與對 Symbol 呼叫Symbol.prototype.toString()類似,但請注意new String(sym)會丟擲錯誤。
Symbol 和 for...in 迭代
Symbol 在 for...in 迭代中不可列舉。此外,Object.getOwnPropertyNames() 不會返回 Symbol 物件屬性,但是,你可以使用 Object.getOwnPropertySymbols() 來獲取這些屬性。
const obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (const i in obj) {
console.log(i);
}
// "c" "d"
Symbol 和 JSON.stringify()
使用 JSON.stringify() 時,Symbol 鍵的屬性將被完全忽略。
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'
欲瞭解更多詳情,請參見 JSON.stringify()。
Symbol 包裝物件作為屬性鍵
當 Symbol 包裝物件用作屬性鍵時,此物件將被強制轉換為其包裝的 Symbol。
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // still 1
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-symbol-objects |
瀏覽器相容性
載入中…
另見
core-js中的SymbolPolyfilltypeof- JavaScript 資料型別和資料結構
- 深入 ES6:Symbol (2015) - hacks.mozilla.org