ES6躬行記(6)——Symbol
本節將會重點分析ES6引入的第6種基本型別:Symbol(符號)。符號可以像字串那樣作為物件的屬性名,只是它有唯一性的特點,可以避免屬性名之間的衝突。
一、建立
符號沒有字面量形式,只能通過Symbol()函式建立。該函式有一個可選的引數,只是用來描述當前符號,除了便於閱讀之外,沒有其他用途。由此可知,即使兩個符號的描述相同,它們還是不能畫等號。注意,Symbol()不是建構函式,因此不能和new運算子組合使用,否則會丟擲型別錯誤。下面用一個例子展示符號的建立。
var sym1 = Symbol(), sym2 = Symbol("name"), sym3 = Symbol("name"), sym4 = new Symbol();//丟擲型別錯誤 console.log(sym2 === sym3);//false
如果要識別一個變數是否為符號,可以用typeof運算子。ES6擴充套件了它,當檢測到符號時,能返回一個新的型別字串“symbol”,具體如下所示。
typeof sym1;//"symbol" typeof sym2;//"symbol"
二、型別轉換
符號在型別轉換時表現得並不靈活,它無法與數字或字串進行運算,也無法顯式的轉換成數字。如下所示,後面四條語句在執行時都會報錯。
var sym = Symbol("age"); Number(sym); parseInt(sym); 1 + sym; "" + sym;
不過,符號可以顯式的轉換成字串或布林值,具體如下所示。
Boolean(sym); //true !sym; //false sym.toString();//"Symbol(age)" String(sym);//"Symbol(age)"
三、全域性共享
ES6會在內部維護一張全域性符號登錄檔,通過Symbol.for()方法,可以登記指定符號,使其變成一個全域性有效地符號,從而達到全域性共享。該方法只接收一個引數,這個引數既是登錄檔中的鍵值,同時也是此符號的描述。下面的程式碼呼叫了兩次Symbol.for()方法,傳遞了相同的引數,返回的卻是同一個全域性符號。
var sym1 = Symbol.for("name"), sym2 = Symbol.for("name"); console.log(sym1 === sym2);//true
在上面的程式碼中,第一次呼叫Symbol.for()方法時,會在登錄檔中搜索鍵值為“name”的符號,由於沒有找到,所以就會建立一個新的符號。而在第二次呼叫Symbol.for()方法時,由於傳遞的鍵值與前一次相同,因此會返回剛剛的那個符號。從而可知,對變數sym1和sym2進行全等比較,返回的結果將是true。
如果要獲取某個全域性符號所對應的鍵值(即它的描述),那麼可以通過Symbol.keyFor()實現,具體操作如下所示。
Symbol.keyFor(sym1);//"name" Symbol.keyFor(sym2);//"name"
四、屬性名
本節開頭曾提到過物件的屬性名可以用符號表示,而這類屬性可以有三種賦值方式。第一種是用方括號,注意,不能用點號,因為點號後面的識別符號會被識別成字串而不是符號。下面程式碼分別用方括號和點號為obj物件的sym屬性賦值,前者被識別成了符號屬性,而後者卻被識別成了字串屬性。
var sym = Symbol("name"), obj = {}; obj[sym] = "strick"; obj.sym = "strick"; console.log(obj);//{Symbol(name): "strick", sym: "strick"}
第二種是在建立物件字面量時,用計算屬性名的方式(即屬性名被方括號包裹)為其賦值,如下所示。
obj = { [sym]: "freedom" };
第三種是呼叫Object.defineProperty()或Object.defineProperties()方法來為符號屬性賦值,如下所示。
Object.defineProperty(obj, sym, { value: "justice" });
注意,符號屬性是不可列舉的,既不能被for-in等迴圈遍歷到,也不能被Object.keys()、Object.getOwnPropertyNames()等方法讀取到。但可以通過Object.getOwnPropertySymbols()方法獲得物件的符號屬性,具體如下所示。
obj = { [sym]: "freedom", age: 28 }; Object.keys(obj); //["age"] Object.getOwnPropertyNames(obj);//["age"] Object.getOwnPropertySymbols(obj);//[Symbol(name)]
五、內建符號
ES6提供了一些內建符號,也叫做知名符號(Well-Known Symbol)。它們暴露了語言的內部邏輯,允許開發人員修改或拓展規範所描述的物件特徵或行為。每一個內建符號對應Symbol物件的一個屬性,例如Symbol.hasInstance、Symbol.iterator等,表1列出了11個內建符號。
表1 內建符號
屬性名稱 | 值型別 | 描述 |
hasInstance | 方法 | 當使用instanceof運算子時會呼叫該方法 |
isConcatSpreadable | 布林值 | 當物件作為Array.prototype.concat()方法的引數時,控制該物件是否被展開 |
iterator | 方法 | 返回一個迭代器,用於定義一個可迭代的物件 |
match | 方法 | 當物件作為String.prototype.match()方法的引數時,該方法會被呼叫 |
replace | 方法 | 當物件作為String.prototype.replace()方法的引數時,該方法會被呼叫 |
search | 方法 | 當物件作為String.prototype.search()方法的引數時,該方法會被呼叫 |
split | 方法 | 當物件作為String.prototype.split()方法的引數時,該方法會被呼叫 |
species | 方法 | 建立派生類的建構函式 |
toPrimitive | 方法 | 當物件需要轉換成原始值(即執行ToPrimitive抽象操作)時,該方法會被呼叫 |
toStringTag | 字串 | 指定物件的型別,可在呼叫Object.prototype.toString()方法的時候返回 |
unscopables | 物件 | 儲存在這個物件中的屬性將不能被with語句所引用 |
下面會給出4個內建符號的示例,分別是hasInstance、isConcatSpreadable、match和toStringTag。
let digit = { [Symbol.hasInstance](number) { return !(number % 2);//判斷數字是否為偶數 } }; 1 instanceof digit;//false 2 instanceof digit;//true let arr1 = [3, 4]; [1, 2].concat(arr1);//[1, 2, 3, 4] let arr2 = [3, 4]; arr2[Symbol.isConcatSpreadable] = false;//禁止展開 [1, 2].concat(arr2);//[1, 2, [3, 4]] let regex = { [Symbol.match](str) { return str.substr(0, 10); } }, message = "My name is strick"; message.match(regex); //"My name is" let people = { [Symbol.toStringTag]: "People" }; people.toString();//"[object People]"