[ ]
和包裝金鑰存取屬性Symbol.iterator
而不是 Symbol.ITERATOR
(等)?
Symbol
符號是 ECMAScript 6 中的一種新的原始類型。它們是透過工廠函式建立的
const
mySymbol
=
Symbol
(
'mySymbol'
);
每次呼叫工廠函式時,都會建立一個新的唯一符號。選用的參數是一個描述字串,它會在列印符號時顯示(它沒有其他用途)
> mySymbol
Symbol(mySymbol)
符號主要用作唯一的屬性鍵,符號絕不會與任何其他屬性鍵 (符號或字串) 衝突。例如,您可以使用儲存在 Symbol.iterator
中的符號作為方法的鍵,讓物件可迭代 (可透過 for-of
迴圈和其他語言機制使用),有關可迭代的詳細資訊,請參閱 迭代章節)
const
iterableObject
=
{
[
Symbol
.
iterator
]()
{
// (A)
···
}
}
for
(
const
x
of
iterableObject
)
{
console
.
log
(
x
);
}
// Output:
// hello
// world
在 A 行中,符號用作方法的鍵。這個唯一的標記讓物件可迭代,並讓我們可以使用 for-of
迴圈。
在 ECMAScript 5 中,您可能使用字串來表示概念,例如顏色。在 ES6 中,您可以使用符號,並確定它們始終是唯一的
const
COLOR_RED
=
Symbol
(
'Red'
);
const
COLOR_ORANGE
=
Symbol
(
'Orange'
);
const
COLOR_YELLOW
=
Symbol
(
'Yellow'
);
const
COLOR_GREEN
=
Symbol
(
'Green'
);
const
COLOR_BLUE
=
Symbol
(
'Blue'
);
const
COLOR_VIOLET
=
Symbol
(
'Violet'
);
function
getComplement
(
color
)
{
switch
(
color
)
{
case
COLOR_RED
:
return
COLOR_GREEN
;
case
COLOR_ORANGE
:
return
COLOR_BLUE
;
case
COLOR_YELLOW
:
return
COLOR_VIOLET
;
case
COLOR_GREEN
:
return
COLOR_RED
;
case
COLOR_BLUE
:
return
COLOR_ORANGE
;
case
COLOR_VIOLET
:
return
COLOR_YELLOW
;
default
:
throw
new
Exception
(
'Unknown color: '
+
color
);
}
}
每次呼叫 Symbol('Red')
時,都會建立一個新的符號。因此,COLOR_RED
絕不會被誤認為另一個值。如果它是字串 'Red'
,情況就會不同。
將符號強制轉換 (隱式轉換) 為字串會擲回例外
const
sym
=
Symbol
(
'desc'
);
const
str1
=
''
+
sym
;
// TypeError
const
str2
=
`
${
sym
}
`
;
// TypeError
唯一的解決方案是明確轉換
const
str2
=
String
(
sym
);
// 'Symbol(desc)'
const
str3
=
sym
.
toString
();
// 'Symbol(desc)'
禁止強制轉換可防止一些錯誤,但也讓使用符號變得更複雜。
下列操作知道符號作為屬性鍵
Reflect.ownKeys()
[]
存取屬性
Object.assign()
下列操作忽略符號作為屬性鍵
Object.keys()
Object.getOwnPropertyNames()
for-in
迴圈ECMAScript 6 引進新的基本型別:符號。它們是作為唯一 ID 的標記。您可以透過工廠函式 Symbol()
建立符號 (如果呼叫為函式,它與傳回字串的 String
類似)
const
symbol1
=
Symbol
();
Symbol()
有個選用的字串值參數,讓您可以為新建立的符號提供說明。在符號轉換為字串時,會使用該說明 (透過 toString()
或 String()
)
> const symbol2 = Symbol('symbol2');
> String(symbol2)
'Symbol(symbol2)'
Symbol()
傳回的每個符號都是唯一的,每個符號都有自己的身分
> Symbol() === Symbol()
false
如果您將 typeof
算子套用至符號,您可以看到符號是基本型別,它會傳回新的符號特定結果
> typeof Symbol()
'symbol'
符號可用作屬性鍵
const
MY_KEY
=
Symbol
();
const
obj
=
{};
obj
[
MY_KEY
]
=
123
;
console
.
log
(
obj
[
MY_KEY
]);
// 123
類別與物件文字有一個稱為計算屬性鍵的功能:你可以透過表達式指定屬性的鍵,方法是將其放入方括弧中。在以下物件文字中,我們使用計算屬性鍵,讓 MY_KEY
的值成為屬性的鍵。
const
MY_KEY
=
Symbol
();
const
obj
=
{
[
MY_KEY
]
:
123
};
方法定義也可以有計算鍵
const
FOO
=
Symbol
();
const
obj
=
{
[
FOO
]()
{
return
'bar'
;
}
};
console
.
log
(
obj
[
FOO
]());
// bar
由於現在有一種新的值可以成為屬性的鍵,因此 ECMAScript 6 使用以下術語
讓我們先建立一個物件,來檢視列舉自有屬性鍵的 API。
const
obj
=
{
[
Symbol
(
'my_key'
)]
:
1
,
enum
:
2
,
nonEnum
:
3
};
Object
.
defineProperty
(
obj
,
'nonEnum'
,
{
enumerable
:
false
});
Object.getOwnPropertyNames()
忽略符號值屬性鍵
> Object.getOwnPropertyNames(obj)
['enum', 'nonEnum']
Object.getOwnPropertySymbols()
忽略字串值屬性鍵
> Object.getOwnPropertySymbols(obj)
[Symbol(my_key)]
Reflect.ownKeys()
考慮所有類型的鍵
> Reflect.ownKeys(obj)
[Symbol(my_key), 'enum', 'nonEnum']
Object.keys()
只考慮可列舉的字串屬性鍵
> Object.keys(obj)
['enum']
Object.keys
這個名稱與新的術語衝突(只列出字串鍵)。現在 Object.names
或 Object.getEnumerableOwnPropertyNames
會是更好的選擇。
在 ECMAScript 5 中,人們通常透過字串表示概念(例如列舉常數)。例如
var
COLOR_RED
=
'Red'
;
var
COLOR_ORANGE
=
'Orange'
;
var
COLOR_YELLOW
=
'Yellow'
;
var
COLOR_GREEN
=
'Green'
;
var
COLOR_BLUE
=
'Blue'
;
var
COLOR_VIOLET
=
'Violet'
;
然而,字串並不像我們希望的那麼獨特。讓我們看看以下函式,了解原因。
function
getComplement
(
color
)
{
switch
(
color
)
{
case
COLOR_RED
:
return
COLOR_GREEN
;
case
COLOR_ORANGE
:
return
COLOR_BLUE
;
case
COLOR_YELLOW
:
return
COLOR_VIOLET
;
case
COLOR_GREEN
:
return
COLOR_RED
;
case
COLOR_BLUE
:
return
COLOR_ORANGE
;
case
COLOR_VIOLET
:
return
COLOR_YELLOW
;
default
:
throw
new
Exception
(
'Unknown color: '
+
color
);
}
}
值得注意的是,你可以使用任意表達式作為 switch
案例,你不會受到任何限制。例如
function
isThree
(
x
)
{
switch
(
x
)
{
case
1
+
1
+
1
:
return
true
;
default
:
return
false
;
}
}
我們使用 switch
提供的彈性,並透過我們的常數(COLOR_RED
等)來參照顏色,而不是硬式編碼它們('Red'
等)。
有趣的是,即使我們這樣做,仍然可能發生混淆。例如,有人可能會定義一個情緒常數
var
MOOD_BLUE
=
'Blue'
;
現在 COLOR_BLUE
的值不再是唯一的了,而且 MOOD_BLUE
可能會被誤認為是它。如果您將它用作 getComplement()
的參數,它會傳回 'Orange'
,而它應該會擲出例外。
讓我們使用符號來修正這個範例。現在我們也可以使用 ES6 功能 const
,它讓我們宣告實際的常數(您無法變更繫結到常數的值,但值本身可能是可變的)。
const
COLOR_RED
=
Symbol
(
'Red'
);
const
COLOR_ORANGE
=
Symbol
(
'Orange'
);
const
COLOR_YELLOW
=
Symbol
(
'Yellow'
);
const
COLOR_GREEN
=
Symbol
(
'Green'
);
const
COLOR_BLUE
=
Symbol
(
'Blue'
);
const
COLOR_VIOLET
=
Symbol
(
'Violet'
);
Symbol
傳回的每個值都是唯一的,這就是為什麼現在沒有其他值會被誤認為是 BLUE
。有趣的是,如果我們使用符號而不是字串,getComplement()
的程式碼完全不會變更,這顯示了它們有多麼相似。
能夠建立金鑰絕不會與其他金鑰衝突的屬性,在兩種情況下很有用
每當 JavaScript 中有繼承層級時(例如透過類別、混入或純粹的原型方法建立),您會有兩種屬性
為了易用性,公開屬性通常會有字串金鑰。但是對於有字串金鑰的私人屬性,意外的名稱衝突可能會變成一個問題。因此,符號是一個好選擇。例如,在以下程式碼中,符號會用於私人屬性 _counter
和 _action
。
const
_counter
=
Symbol
(
'counter'
);
const
_action
=
Symbol
(
'action'
);
class
Countdown
{
constructor
(
counter
,
action
)
{
this
[
_counter
]
=
counter
;
this
[
_action
]
=
action
;
}
dec
()
{
let
counter
=
this
[
_counter
];
if
(
counter
<
1
)
return
;
counter
--
;
this
[
_counter
]
=
counter
;
if
(
counter
===
0
)
{
this
[
_action
]();
}
}
}
請注意,符號只能保護您免於名稱衝突,無法保護您免於未經授權的存取,因為您可以透過 Reflect.ownKeys()
找出物件的所有自有屬性金鑰,包括符號。如果您也想要在那裡受到保護,您可以使用「類別的私人資料」一節中列出的其中一種方法。
具有唯一識別碼的符號使其成為不同層級上存在於「一般」屬性金鑰的公用屬性金鑰的理想選擇,因為元層級金鑰和一般金鑰不得衝突。元層級屬性的範例之一是物件可實作的方法,用以自訂其在函式庫中的處理方式。使用符號金鑰可避免函式庫誤將一般方法當成自訂方法。
ES6 可迭代性即為其中一種自訂方式。如果物件的方法金鑰為 (儲存在) Symbol.iterator
中的符號,則該物件為可迭代的。在以下程式碼中,obj
為可迭代的。
const
obj
=
{
data
:
[
'hello'
,
'world'
],
[
Symbol
.
iterator
]()
{
···
}
};
obj
的可迭代性讓您可以使用 for-of
迴圈和類似的 JavaScript 功能
for
(
const
x
of
obj
)
{
console
.
log
(
x
);
}
// Output:
// hello
// world
如果您認為名稱衝突無關緊要,以下列舉三個名稱衝突導致 JavaScript 標準函式庫演進過程中出現問題的範例
Array.prototype.values()
時,它中斷了使用 with
搭配陣列並在外部範圍隱藏變數 values
的現有程式碼 (錯誤報告 1,錯誤報告 2)。因此,引入了隱藏屬性免受 with
影響的機制 (Symbol.unscopables
)。String.prototype.contains
與 MooTools 新增的方法衝突,因此必須重新命名為 String.prototype.includes
(錯誤報告)。Array.prototype.contains
也與 MooTools 新增的方法衝突,因此必須重新命名為 Array.prototype.includes
(錯誤報告)。相反地,透過屬性金鑰 Symbol.iterator
為物件新增可迭代性不會造成問題,因為該金鑰不會與任何項目衝突。
下表顯示如果您明確或隱式地將符號轉換為其他基本型別會發生什麼情況
轉換為 | 明確轉換 | 強制轉換(隱式轉換) |
---|---|---|
布林值 |
Boolean(sym) → OK |
!sym → OK |
數字 |
Number(sym) → TypeError
|
sym*2 → TypeError
|
字串 |
String(sym) → OK |
''+sym → TypeError
|
sym.toString() → OK |
`${sym}` → TypeError
|
禁止強制轉換為字串很容易讓你絆倒
const
sym
=
Symbol
();
console
.
log
(
'A symbol: '
+
sym
);
// TypeError
console
.
log
(
`A symbol:
${
sym
}
`
);
// TypeError
要修復這些問題,你需要明確轉換為字串
console
.
log
(
'A symbol: '
+
String
(
sym
));
// OK
console
.
log
(
`A symbol:
${
String
(
sym
)
}
`
);
// OK
符號通常禁止強制轉換(隱式轉換)。本節說明原因。
強制轉換為布林值總是允許的,主要是為了啟用 if
陳述式和其他位置的真值檢查
if
(
value
)
{
···
}
param
=
param
||
0
;
符號是特殊的屬性金鑰,這就是為什麼你想要避免意外將它們轉換為字串的原因,而字串是不同類型的屬性金鑰。如果你使用加法運算子來計算屬性的名稱,可能會發生這種情況
myObject
[
'__'
+
value
]
這就是為什麼如果 value
是符號,就會擲出 TypeError
。
你也不想意外地將符號轉換為陣列索引。以下是可能發生這種情況的程式碼,如果 value
是符號
myArray
[
1
+
value
]
這就是為什麼加法運算子在此情況下會擲出錯誤。
要明確將符號轉換為布林值,你可以呼叫 Boolean()
,它會為符號傳回 true
> const sym = Symbol('hello');
> Boolean(sym)
true
Boolean()
透過內部運算 ToBoolean()
計算其結果,它會為符號和其他真值傳回 true
。
強制轉換也使用 ToBoolean()
> !sym
false
若要明確將符號轉換為數字,請呼叫 Number()
> const sym = Symbol('hello');
> Number(sym)
TypeError: can't convert symbol to number
Number()
透過內部運算 ToNumber()
計算其結果,而此運算會對符號擲回 TypeError
。
強制轉換也會使用 ToNumber()
> +sym
TypeError: can't convert symbol to number
若要明確將符號轉換為字串,請呼叫 String()
> const sym = Symbol('hello');
> String(sym)
'Symbol(hello)'
如果 String()
的參數是符號,則它會自行處理轉換為字串,並傳回以建立符號時提供的描述為封裝的字串 Symbol()
。如果未提供描述,則會使用空字串
> String(Symbol())
'Symbol()'
toString()
方法傳回的字串與 String()
相同,但這兩個運算都不會呼叫對方,它們都呼叫相同的內部運算 SymbolDescriptiveString()
。
> Symbol('hello').toString()
'Symbol(hello)'
強制轉換透過內部運算 ToString()
處理,而此運算會對符號擲回 TypeError
。一種會強制轉換其參數為字串的方法是 Number.parseInt()
> Number.parseInt(Symbol())
TypeError: can't convert symbol to string
+
) 轉換 加法運算子 的運作方式如下
ToString()
),將它們串接,並傳回結果。強制轉換為字串或數字會擲回例外,這表示您無法 (直接) 對符號使用加法運算子
> '' + Symbol()
TypeError: can't convert symbol to string
> 1 + Symbol()
TypeError: can't convert symbol to number
雖然所有其他基本型別值都有字面值,但您需要透過函式呼叫 Symbol
來建立符號。因此,有意外呼叫 Symbol
作為建構函式的風險。這會產生 Symbol
的執行個體,而它們並非十分有用。因此,當您嘗試這麼做時,會擲回例外
> new Symbol()
TypeError: Symbol is not a constructor
仍然有辦法建立包裝物件,也就是 Symbol
的執行個體:Object
,作為函式呼叫,會將所有值轉換為物件,包括符號。
> const sym = Symbol();
> typeof sym
'symbol'
> const wrapper = Object(sym);
> typeof wrapper
'object'
> wrapper instanceof Symbol
true
[ ]
和包裝鍵存取屬性 方括號運算子 [ ]
通常會強制轉換其運算元為字串。現在有兩個例外:符號包裝物件會解開包裝,而符號則會原樣使用。讓我們使用下列物件來檢視此現象。
const
sym
=
Symbol
(
'yes'
);
const
obj
=
{
[
sym
]
:
'a'
,
str
:
'b'
,
};
方括號運算子會解開包裝符號的包裝
> const wrappedSymbol = Object(sym);
> typeof wrappedSymbol
'object'
> obj[wrappedSymbol]
'a'
與任何其他與符號無關的值一樣,包裝字串會透過方括號運算子轉換為字串
> const wrappedString = new String('str');
> typeof wrappedString
'object'
> obj[wrappedString]
'b'
用於取得和設定屬性的運算子使用內部運算 ToPropertyKey()
,其運作方式如下
ToPrimitive()
將運算元轉換為基元,偏好的類型為 String
[@@toPrimitive]()
,則使用該方法將其轉換為基元值。符號有此方法,它會傳回封裝的符號。toString()
將運算元轉換為基元(如果它傳回基元值)。否則,使用 valueOf()
(如果它傳回基元值)。否則,會擲回 TypeError
。偏好的類型 String
決定先呼叫 toString()
,再呼叫 valueOf()
。ToString()
將結果強制轉換為字串。程式碼領域(簡稱:領域)是程式碼片段存在的內容。它包含全域變數、載入的模組等。儘管程式碼存在於「單一」領域「內部」,它仍可能存取其他領域中的程式碼。例如,瀏覽器中的每個框架都有自己的領域。而且執行可以從一個框架跳到另一個框架,如下列 HTML 所示。
<
head
>
<
script
>
function
test
(
arr
)
{
var
iframe
=
frames
[
0
];
// This code and the iframe’s code exist in
// different realms. Therefore, global variables
// such as Array are different:
console
.
log
(
Array
===
iframe
.
Array
);
// false
console
.
log
(
arr
instanceof
Array
);
// false
console
.
log
(
arr
instanceof
iframe
.
Array
);
// true
// But: symbols are the same
console
.
log
(
Symbol
.
iterator
===
iframe
.
Symbol
.
iterator
);
// true
}
</
script
>
</
head
>
<
body
>
<
iframe
srcdoc
=
"<script>window.parent.test([])</script>"
>
</
iframe
>
</
body
>
問題在於每個領域都有自己的全域變數,其中每個變數 Array
指向不同的物件,儘管它們本質上都是同一個物件。類似地,函式庫和使用者程式碼會在每個領域中載入一次,而且每個領域都有相同物件的不同版本。
物件會依識別碼進行比較,但布林值、數字和字串會依值進行比較。因此,不論數字 123 來自哪個領域,它都與其他所有 123 無法區分。這類似於數字文字 123
始終產生相同的值。
符號有單獨的識別碼,因此無法像其他基元值一樣順利跨越領域。這對應當跨越領域運作的符號(例如 Symbol.iterator
)來說是個問題:如果物件在一個領域中可迭代,則它應在所有領域中都可迭代。所有內建符號都由 JavaScript 引擎管理,這會確保例如 Symbol.iterator
在每個領域中都是相同的值。如果函式庫想要提供跨領域符號,則它必須依賴額外的支援,而這種支援以全域符號註冊的形式提供:此註冊對所有領域都是全域的,而且會將字串對應到符號。對於每個符號,函式庫需要想出一個儘可能唯一的字串。它不會使用 Symbol()
來建立符號,而是詢問註冊表將字串對應到的符號。如果註冊表已經有該字串的項目,則會傳回關聯的符號。否則,會先建立項目和符號。
您透過 Symbol.for()
向註冊表索取符號,並透過 Symbol.keyFor()
擷取與符號關聯的字串(其金鑰)
> const sym = Symbol.for('Hello everybody!');
> Symbol.keyFor(sym)
'Hello everybody!'
由 JavaScript 引擎提供的跨領域符號(例如 Symbol.iterator
)不在註冊表中
> Symbol.keyFor(Symbol.iterator)
undefined
符號的原始計畫是支援私有屬性(會有公開和私有符號)。但此功能已取消,因為使用「get」和「set」(兩個元物件協定操作)來管理私有資料與代理程式互動不佳
這兩個目標互相衝突。類別章節說明 管理私有資料的選項。符號是其中一種選項,但您無法獲得與私有符號相同程度的安全性,因為可以透過 Object.getOwnPropertySymbols()
和 Reflect.ownKeys()
來判斷用作物件屬性金鑰的符號。
在某些方面,符號類似於基本型別值,在其他方面,它們類似於物件
那麼符號是什麼 - 基本型別值還是物件?最後,它們變成了基本型別值,原因有兩個。
首先,符號更類似於字串,而不是物件:它們是語言的基本值,不可變,並且可用作屬性金鑰。符號具有唯一識別碼並不一定與它們類似於字串相矛盾:UUID 演算法會產生近乎唯一的字串。
其次,符號最常被用作屬性金鑰,因此針對此用例最佳化 JavaScript 規格和實作很有意義。然後符號不需要物件的許多功能
instanceof
、Object.keys()
等方式進行檢查。符號不具備這些功能,讓規格和實作變得更簡單。V8 團隊也表示,在處理屬性鍵時,將原始類型設為特殊情況比某些物件更容易。
與字串不同,符號是唯一的,可避免名稱衝突。這對於顏色等代幣來說很方便,但對於支援元層級方法(例如其鍵為 Symbol.iterator
的方法)來說至關重要。Python 使用特殊名稱 __iter__
來避免衝突。你可以保留雙底線名稱給程式語言機制使用,但程式庫該怎麼辦?有了符號,我們就有了適用於所有人的擴充機制。如你稍後在公開符號部分所見,JavaScript 本身已經廣泛使用此機制。
在無衝突屬性鍵方面,有一個假設的符號替代方案:使用命名慣例。例如,帶有 URL 的字串(例如 'http://example.com/iterator'
)。但這會引入第二類屬性鍵(相對於通常是有效識別碼且不包含冒號、斜線、點等符號的「一般」屬性名稱),這基本上就是符號的本質。那麼我們不如直接引入一種新的值。
不,它們不同。
Ruby 的符號基本上是建立值的文字。兩次提到同一個符號會產生同一個值兩次
:foo
==
:foo
JavaScript 函式 Symbol()
是符號的工廠,它傳回的每個值都是唯一的
Symbol
(
'foo'
)
!==
Symbol
(
'foo'
)
Symbol.iterator
而不是 Symbol.ITERATOR
(等)? 已知符號儲存在名稱以小寫字元開頭且使用駝峰式大小寫的屬性中。在某種程度上,這些屬性是常數,而常數通常會使用全大寫名稱(例如 Math.PI
)。但它們的拼寫原因不同:已知符號用於取代一般屬性鍵,這就是為什麼它們的「名稱」遵循屬性鍵規則,而不是常數規則。
本節概述 ECMAScript 6 的符號 API。
Symbol
Symbol(description?) : symbol
建立一個新的符號。選用的參數 description
允許您為符號提供描述。存取描述的唯一方法是將符號轉換為字串(透過 toString()
或 String()
)。此類轉換的結果為 'Symbol('+description+')'
> const sym = Symbol('hello');
> String(sym)
'Symbol(hello)'
Symbol
無法用作建構函數 – 如果您透過 new
呼叫它,將擲回例外。
符號唯一有用的方法是 toString()
(透過 Symbol.prototype.toString()
)。
轉換為 | 明確轉換 | 強制轉換(隱式轉換) |
---|---|---|
布林值 |
Boolean(sym) → OK |
!sym → OK |
數字 |
Number(sym) → TypeError
|
sym*2 → TypeError
|
字串 |
String(sym) → OK |
''+sym → TypeError
|
sym.toString() → OK |
`${sym}` → TypeError
|
|
物件 |
Object(sym) → OK |
Object.keys(sym) → OK |
全域物件 Symbol
有幾個屬性,用作所謂已知符號的常數。這些符號讓您可以使用它們作為屬性金鑰,來設定 ES6 如何處理物件。以下是 所有已知符號 的清單
Symbol.hasInstance
(方法)C
自訂 x instanceof C
的行為。Symbol.toPrimitive
(方法)Symbol.toStringTag
(字串)Object.prototype.toString()
呼叫,以計算物件 obj
的預設字串說明。
'[object '
+
obj
[
Symbol
.
toStringTag
]
+
']'
Symbol.unscopables
(物件)with
陳述式隱藏一些屬性。Symbol.iterator
(方法)for-of
迴圈和散佈運算子 (...
))進行反覆運算)。此方法傳回一個反覆運算器。詳細資訊:「可反覆運算和反覆運算器」章節。String.prototype.match(x, ···)
轉發至 x[Symbol.match](···)
。String.prototype.replace(x, ···)
轉發至 x[Symbol.replace](···)
。String.prototype.search(x, ···)
轉發至 x[Symbol.search](···)
。String.prototype.split(x, ···)
轉發至 x[Symbol.split](···)
。詳細說明請見字串章節的「將正規表示式工作委派給參數的字串方法」一節。
如果您希望符號在所有領域都相同,則需要透過以下兩種方法使用全域符號註冊表
Symbol.for(str) : symbol
str
的符號。如果 str
尚未在註冊表中,則會建立新的符號並以金鑰 str
歸檔在註冊表中。Symbol.keyFor(sym) : string
sym
關聯的字串。如果 sym
不在註冊表中,則此方法會傳回 undefined
。此方法可 used to serialize symbols (e.g. to JSON)。