符號是透過工廠函式 Symbol()
建立的基本類型值
const mySymbol = Symbol('mySymbol');
參數是選用的,用於提供描述,主要用於除錯。
符號是基本類型值
它們必須透過 typeof
分類
const sym = Symbol();
.equal(typeof sym, 'symbol'); assert
它們可以是物件中的屬性鍵
const obj = {
: 123,
[sym]; }
即使符號是基本類型,它們也類似物件,因為透過 Symbol()
建立的每個值都是唯一的,且不會透過值進行比較
> Symbol() === Symbol()false
在符號之前,如果我們需要唯一(僅等於它們自己)的值,物件是最佳選擇
const string1 = 'abc';
const string2 = 'abc';
.equal(
assert=== string2, true); // not unique
string1
const object1 = {};
const object2 = {};
.equal(
assert=== object2, false); // unique
object1
const symbol1 = Symbol();
const symbol2 = Symbol();
.equal(
assert=== symbol2, false); // unique symbol1
我們傳遞給符號工廠函式的參數會提供已建立符號的描述
const mySymbol = Symbol('mySymbol');
描述可以用兩種方式存取。
首先,它是 .toString()
傳回的字串的一部分
.equal(mySymbol.toString(), 'Symbol(mySymbol)'); assert
其次,自 ES2019 以來,我們可以透過屬性 .description
擷取描述
.equal(mySymbol.description, 'mySymbol'); assert
符號的主要用例是
假設您想建立代表紅色、橙色、黃色、綠色、藍色和紫色的常數。一種簡單的方法是使用字串
const COLOR_BLUE = 'Blue';
好處是記錄該常數會產生有用的輸出。壞處是,由於內容相同的兩個字串被視為相等,因此有將不相關的值誤認為顏色的風險
const MOOD_BLUE = 'Blue';
.equal(COLOR_BLUE, MOOD_BLUE); assert
我們可以透過符號來解決此問題
const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');
.notEqual(COLOR_BLUE, MOOD_BLUE); assert
讓我們使用符號值常數來實作函式
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);
}
}.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET); assert
物件中屬性(欄位)的金鑰在兩個層級使用
程式在基本層級運作。該層級的金鑰反映問題領域,也就是程式解決問題的區域,例如
ECMAScript 和許多函式庫在元層級運作。它們管理資料並提供不屬於問題領域的服務,例如
ECMAScript 在建立物件的字串表示形式時會使用標準方法 .toString()
(第 A 行)
const point = {
x: 7,
y: 4,
toString() {
return `(${this.x}, ${this.y})`;
,
};
}.equal(
assertString(point), '(7, 4)'); // (A)
.x
和 .y
是基本層級屬性,用於解決計算點的問題。.toString()
是元層級屬性,與問題領域無關
ECMAScript 標準方法 .toJSON()
const point = {
x: 7,
y: 4,
toJSON() {
return [this.x, this.y];
,
};
}.equal(
assertJSON.stringify(point), '[7,4]');
.x
和 .y
是基本層級屬性,.toJSON()
是元層級屬性
程式的基本層級和元層級必須獨立:基本層級屬性金鑰不應與元層級屬性金鑰衝突
如果我們使用名稱(字串)作為屬性金鑰,我們將面臨兩個挑戰
當語言第一次建立時,它可以使用任何想要的元層級名稱。基本層級程式碼被迫避免這些名稱。然而,稍後,當許多基本層級程式碼已經存在時,元層級名稱就不能再自由選擇了
我們可以引入命名規則來區分基本層級和元層級。例如,Python 使用兩個底線將元層級名稱括起來:__init__
、__iter__
、__hash__
等。然而,語言的元層級名稱和函式庫的元層級名稱仍然存在於同一個命名空間中,並且可能會發生衝突
以下兩個範例說明後者對 JavaScript 來說是一個問題
2018 年 5 月,陣列方法 .flatten()
必須重新命名為 .flat()
,因為前一個名稱已被函式庫使用(來源)
2020 年 11 月,陣列方法 .item()
必須重新命名為 .at()
,因為前一個名稱已被函式庫使用(來源)
用作屬性金鑰的符號可以幫助我們解決此問題:每個符號都是唯一的,而且符號金鑰絕不會與任何其他字串或符號金鑰發生衝突
舉例來說,假設我們正在撰寫一個函式庫,如果物件實作特殊方法,則函式庫會以不同的方式處理物件。以下是定義此類方法的屬性金鑰並為物件實作該方法的範例
const specialMethod = Symbol('specialMethod');
const obj = {
_id: 'kf12oi',
// (A)
[specialMethod]() { return this._id;
};
}.equal(obj[specialMethod](), 'kf12oi'); assert
第 A 行中的方括號使我們能夠指定方法必須具有金鑰 specialMethod
。更多詳細說明請參閱 §28.7.2「物件文字中的運算金鑰」。
在 ECMAScript 中扮演特殊角色的符號稱為公開已知的符號。範例包括
Symbol.iterator
:使物件可迭代。這是傳回迭代器的某個方法的金鑰。有關此主題的更多資訊,請參閱 §30「同步迭代」。
Symbol.hasInstance
:自訂 instanceof
的運作方式。如果物件實作具有該金鑰的方法,則可以在該運算式的右側使用它。例如
const PrimitiveNull = {
Symbol.hasInstance](x) {
[return x === null;
};
}.equal(null instanceof PrimitiveNull, true); assert
Symbol.toStringTag
:影響預設的 .toString()
方法。
> String({})'[object Object]'
> String({ [Symbol.toStringTag]: 'is no money' })'[object is no money]'
注意:通常最好覆寫 .toString()
。
練習:公開已知的符號
Symbol.toStringTag
:exercises/symbols/to_string_tag_test.mjs
Symbol.hasInstance
:exercises/symbols/has_instance_test.mjs
如果我們將符號 sym
轉換為另一種基本類型,會發生什麼事?表格 15 有答案。
轉換為 | 明確轉換 | 強制轉換(隱含轉換) |
---|---|---|
布林 | Boolean(sym) → OK |
!sym → OK |
數字 | Number(sym) → TypeError |
sym*2 → TypeError |
字串 | String(sym) → OK |
''+sym → TypeError |
sym.toString() → OK |
`${sym}` → TypeError |
符號的一個主要缺點是將它們轉換為其他東西時,會經常擲出例外。背後的思考是什麼?首先,轉換為數字永遠沒有意義,應該發出警告。其次,將符號轉換為字串確實有助於診斷輸出。但警告意外將符號轉換為字串(這是一種不同類型的屬性金鑰)也是有意義的
const obj = {};
const sym = Symbol();
.throws(
assert=> { obj['__'+sym+'__'] = true },
() message: 'Cannot convert a Symbol value to a string' }); {
缺點是例外會讓使用符號變得更複雜。您必須在透過加號運算子組裝字串時明確轉換符號
> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbolTypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)'Symbol I used: Symbol(mySymbol)'
測驗
請參閱 測驗應用程式。