給急躁的程式設計師的 JavaScript(ES2022 版)
請支持這本書:購買捐款
(廣告,請不要封鎖。)

22 符號



22.1 符號是基本類型,但同時也類似物件

符號是透過工廠函式 Symbol() 建立的基本類型值

const mySymbol = Symbol('mySymbol');

參數是選用的,用於提供描述,主要用於除錯。

22.1.1 符號是基本類型值

符號是基本類型值

22.1.2 符號也類似物件

即使符號是基本類型,它們也類似物件,因為透過 Symbol() 建立的每個值都是唯一的,且不會透過值進行比較

> Symbol() === Symbol()
false

在符號之前,如果我們需要唯一(僅等於它們自己)的值,物件是最佳選擇

const string1 = 'abc';
const string2 = 'abc';
assert.equal(
  string1 === string2, true); // not unique

const object1 = {};
const object2 = {};
assert.equal(
  object1 === object2, false); // unique

const symbol1 = Symbol();
const symbol2 = Symbol();
assert.equal(
  symbol1 === symbol2, false); // unique

22.2 符號的描述

我們傳遞給符號工廠函式的參數會提供已建立符號的描述

const mySymbol = Symbol('mySymbol');

描述可以用兩種方式存取。

首先,它是 .toString() 傳回的字串的一部分

assert.equal(mySymbol.toString(), 'Symbol(mySymbol)');

其次,自 ES2019 以來,我們可以透過屬性 .description 擷取描述

assert.equal(mySymbol.description, 'mySymbol');

22.3 符號的用例

符號的主要用例是

22.3.1 符號作為常數值

假設您想建立代表紅色、橙色、黃色、綠色、藍色和紫色的常數。一種簡單的方法是使用字串

const COLOR_BLUE = 'Blue';

好處是記錄該常數會產生有用的輸出。壞處是,由於內容相同的兩個字串被視為相等,因此有將不相關的值誤認為顏色的風險

const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);

我們可以透過符號來解決此問題

const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');

assert.notEqual(COLOR_BLUE, MOOD_BLUE);

讓我們使用符號值常數來實作函式

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);
  }
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);

22.3.2 符號作為唯一的屬性金鑰

物件中屬性(欄位)的金鑰在兩個層級使用

程式的基本層級和元層級必須獨立:基本層級屬性金鑰不應與元層級屬性金鑰衝突

如果我們使用名稱(字串)作為屬性金鑰,我們將面臨兩個挑戰

以下兩個範例說明後者對 JavaScript 來說是一個問題

用作屬性金鑰的符號可以幫助我們解決此問題:每個符號都是唯一的,而且符號金鑰絕不會與任何其他字串或符號金鑰發生衝突

22.3.2.1 範例:具有元層級方法的函式庫

舉例來說,假設我們正在撰寫一個函式庫,如果物件實作特殊方法,則函式庫會以不同的方式處理物件。以下是定義此類方法的屬性金鑰並為物件實作該方法的範例

const specialMethod = Symbol('specialMethod');
const obj = {
  _id: 'kf12oi',
  [specialMethod]() { // (A)
    return this._id;
  }
};
assert.equal(obj[specialMethod](), 'kf12oi');

第 A 行中的方括號使我們能夠指定方法必須具有金鑰 specialMethod。更多詳細說明請參閱 §28.7.2「物件文字中的運算金鑰」

22.4 公開已知的符號

在 ECMAScript 中扮演特殊角色的符號稱為公開已知的符號。範例包括

  練習:公開已知的符號

22.5 轉換符號

如果我們將符號 sym 轉換為另一種基本類型,會發生什麼事?表格 15 有答案。

表格 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();
assert.throws(
  () => { obj['__'+sym+'__'] = true },
  { message: 'Cannot convert a Symbol value to a string' });

缺點是例外會讓使用符號變得更複雜。您必須在透過加號運算子組裝字串時明確轉換符號

> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)'

  測驗

請參閱 測驗應用程式