跳到主要內容
黯羽輕揚每天積累一點點

Symbol_ES6 筆記 7

免費2016-05-08#JS#es6 symbol#js symbol#js symbol是什么

Symbol 算是一個精緻的特性,解決了擴展現有 API 的大問題

一。Symbol 是什麼

typeof Symbol() === 'symbol',symbol 是 js 中第 7 種基本類型(本來就有的 6 種是 null, undefined, Number, Boolean, Object, String),不是字符串也不是對象

作用:symbol 用來避免命名衝突,解決了篡改(添加屬性)原生對象的後遺症,不用擔心屬性名以後和原生屬性名或者其它類庫操作衝突

二。語法

獲取 Symbol 有 3 種方式,如下:

###1.Symbol(desc)

返回 symbol,desc 可選,symbol.toString() 返回`Symbol(${desc})`,例如:

var obj = {
    a: 1
};

// 不用 new,Symbol 不是構造器
var safeKey = Symbol();
obj[safeKey] = 'value';
console.log(obj[safeKey]);  // value

var anotherSafeKey = Symbol('isAnimActive');
console.log(anotherSafeKey);    // Symbol(isAnimActive)

上面的 var safeKey = Symbol(); 看起來比較奇怪,new 操作符呢?Symbol 不是構造器,不能通過 new 操作符調用,非要 new 的話,會得到這樣一個錯誤:

Uncaught TypeError: Symbol is not a constructor

這確實比較奇怪,編碼規範中一般首字母大寫表示類型名,理應通過 new 操作符調用,但 API 給出的就是一個不合編碼規範的 Symbol 函數

特點:

  • Symbol('key') !== Symbol('key'),每次調用 Symbol() 返回的都是不一樣的值

  • Symbol 可以用作屬性名,而且和任何東西(字符串、數字、其它 Symbol)都不相等,所以 Symbol 類型的屬性名不會和任何已有屬性名衝突,也不會和將來的任何新屬性名衝突

  • for...inObject.keys(obj)Object.getOwnPropertyNames(obj) 會跳過 symbol 屬性

  • Object.getOwnPropertySymbols(obj) 返回對象所有的 symbol 屬性名

  • Reflect.ownKeys(obj) 返回對象的所有屬性名(包括 symbol 屬性名和字符串屬性名)

  • symbol 只讀,類似於字符串,嚴格模式下給 symbol 添加屬性會報錯 TypeError

  • symbol不會自動轉換為字符串,嘗試拼接 symbol 會報錯 TypeError,可以手動調用 toString() 再拼接

示例(接著上一個示例)如下:

obj[Symbol('ready')] = true;
//!!! undefined
// 因為特點 1
console.log(obj[Symbol('ready')]);  // undefined
console.log(obj);   // Object {Symbol(): "value", Symbol(ready): true}
// 跳過 symbol 屬性
for (var key in obj) {
    console.log(`obj[${key}] = ${obj[key]}`);
}   // obj[a] = 1

// 獲取所有 obj 上所有 Symbol 類型的屬性名
console.log(Object.getOwnPropertySymbols(obj)); // Array [ Symbol(), Symbol(ready) ]
// 獲取 objs 上的所有屬性名
console.log(Reflect.ownKeys(obj));  // Array [ "a", Symbol(), Symbol(ready) ]

// 只讀
var s = Symbol();
s.a = 1;
console.log(s.a);   // undefined

// 不會自動轉字符串
// console.log(Symbol('123') + '4');   // TypeError: can't convert symbol to string
console.log(Symbol('123').toString() + '4');    // Symbol(123)4

###2.Symbol.for(str)

表示 symbol 註冊表,用來創建共享 symbol,特點如下:

  • Symbol.for('ready') === Symbol.for('ready')

  • Symbol('str') !== Symbol.for('str')

示例(接著前面的所有示例):

// Symbol.for()
obj[Symbol.for('ready')] = 'ready';
console.log(obj[Symbol.for('ready')]);  // ready
console.log(Object.getOwnPropertySymbols(obj));
// log print: Array [ Symbol(), Symbol(ready), Symbol(ready) ]

P.S. 最後輸出的數組中有兩個 Symbol(ready),但它們對應的 symbol 不相等,只是 toString 返回的結果相同(特點 2)

注意:共享 Symbol 的話,只能保證屬性名不與將來的 DOM API 衝突,不能保證不與其它代碼衝突。類庫作者不應該使用它,建議只在業務代碼中需要跨模組或者跨頁面 Symbol 共享時使用

###3.Symbol.xxx

用來獲取原生 Symbol,例如 Symbol.iterator,特點如下:

  • 新 API 向後兼容,比如實現 iterable 接口:obj[Symbol.iterator] = gen,不會影響舊代碼

  • 準備好了 hook,藉助 symbol 以後新特性 API 都不會影響舊代碼

尚未實現的新特性:

  • Symbol.hasInstance 擴展 instanceof

  • Symbol.unscopables 阻止方法加入動態作用域

  • Symbol.match 擴展 str.match

這些 Symbol 已經作為原生 Symbol 預留出來了,很快就會實現,以後的(增強現有 API 的)新特性都可以通過 Symbol簡單安全地添加

三。應用場景

###1. 給 DOM 元素添個標記屬性

可能多數時候不需要給 DOM 元素添加自定義屬性,因為這存在副作用(自定義屬性名可能與其它代碼衝突,或者與將來的 DOM API 衝突),於是一般都選擇維護一個表結構來保存元素對應的狀態等附加信息,每次查表獲得目標元素的附加信息,如果表很大,查表會比較耗時,如果表結構比較複雜,查表操作本身也會變得很麻煩。。。等等,我們為什麼要查表獲取元素的附加信息?因為副作用,那如果這個副作用沒了呢?

Symbol 就是用來消除這個副作用的,元素相關的信息,直接以 Symbol 對象為 key,添在 DOM 元素上就可以了,簡單粗暴安全有效,例如:

var IS_MOVING = Symbol('isMoving');
var INFO = Symbol('info');

if (!elem[IS_MOVING]) {
    anim(elem);
    elem[IS_MOVING] = true;
    elem[INFO] = {
        step: 1;
    };
}

需要持有自定義的 Symbol 類型的 key,把該 key 放在自己的作用域裡,就不會有任何副作用了

###2. 生成唯一的 key

以前生成唯一的 key 可能需要一個稍複雜的小算法,而現在 Symbol 是最簡單的唯一 key 生成方法,例如:

// 不關注屬性名,只是想簡單地存取 obj
var data = {
    dir: {},
    save(val) {
        var key = Symbol();
        this.dir[key] = val;
        return key;
    },
    get(key) {
        return this.dir[key];
    }
}
var key = data.save('data');
console.log(data.get(key)); // data

###3. 通過原生 Symbol 實現支持的 API 擴展

標準將提供越來越多的 hook,讓我們來實現 API 擴展方法,例如讓自定義對象可迭代:

var obj = {
'a': 1,
'b': 2
}
// generator 實現迭代器
obj[Symbol.iterator] = function*() {
    for (var key in this) {
        yield this[key];
    }
}
for (var val of obj) {
    console.log(val);
}

四。總結

Symbol 算是一個精緻的特性,解決了擴展現有 API 的大問題(給新特性提供了一套 hook 機制,以後可以安全地擴展)

參考資料

  • 《ES6 in Depth》:InfoQ 中文站提供的免費電子書

評論

暫無評論,快來發表你的看法吧

提交評論