在 symbols 誕生之前,對象的鍵只能是字符串。假如我們試着使用一個非字符串當做對象的鍵,就會被轉換為字符串,如下所示:
const obj = {}; obj.foo = 'foo'; obj['bar'] = 'bar'; obj[2] = 2; obj[{}] = 'someobj'; console.log(obj); // { '2': 2, foo: 'foo', bar: 'bar', '[object Object]': 'someobj' }
symbols 是什么?
- symbols 是一種無法被重建的基本類型。這時 symbols 有點類似與對象創建的實例互相不相等的情況,但同時 symbols 又是一種無法被改變的基本類型數據。
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2); // false
當你初始化一個帶有一個接收可選字符串參數的 symbols 時,我們可以來看下,除此之外看看它會否影響自身。
const s1 = Symbol('debug'); const str = 'debug'; const s2 = Symbol('xxyy'); console.log(s1 === str); // false console.log(s1 === s2); // false console.log(s1); // Symbol(debug)
symbols 作為對象的屬性
- symbols 有另一個很重要的用途,就是用作對象的 key。這兒有一個 symbols 作為對象 key 使用的例子:
const obj = {}; const sym = Symbol(); obj[sym] = 'foo'; obj.bar = 'bar'; console.log(obj); // { bar: 'bar' } console.log(sym in obj); // true console.log(obj[sym]); // foo console.log(Object.keys(obj)); // ['bar']
-
我們注意到使用 Object.keys() 並沒有返回 symbols,這是為了向后兼容性的考慮。老代碼不兼容 symbols,因此古老的 Object.keys() 不應該返回 symbols。
-
看第一眼,我們可能會覺得 symbols 這個特性很適合作為對象的私有屬性,許多其他語言都要類似的類的隱藏屬性,這一直被認為是 JavaScript 的一大短板。不幸的是,還是有可能通過 symbols 來取到對象的值,甚至都不用試着獲取對象屬性就可以得到對象 key,例如,通過 Reflect.ownKeys() 方法就可以獲取所有的 key,包括 字符串和 symbols,如下所示:
function tryToAddPrivate(o) { o[Symbol('Pseudo Private')] = 42; } const obj = { prop: 'hello' }; tryToAddPrivate(obj); console.log(Reflect.ownKeys(obj));// [ 'prop', Symbol(Pseudo Private) ] console.log(obj[Reflect.ownKeys(obj)[1]]); // 42
注意:現在已經有一個旨在解決 JavaScript 私有屬性的提案,叫做 Private Fields,盡管這並不會使所有的對象受益,它仍然對對象的實例有用,Private Fields 在 Chrome 74版本可用。
阻止對象屬性名沖突
- symbols 可能對對象的私有屬性沒有直接好處,但是它有另外一個用途,它在不知道對象原有屬性名的情況下,擴展對象屬性很有用。
- 考慮一下當兩個不同的庫要讀取對象的一些原始屬性時,或許它們都想要類似的標識符。如果只是簡單的使用字符串 id 作為 key,這將會有很大的風險,因為它們的 key 完全有可能相同。
function lib1tag(obj) { obj.id = 42; } function lib2tag(obj) { obj.id = 369; }
通過使用 symbols,不同的庫在初始化的時候生成其所需的 symbols,然后就可以在對象上任意賦值。
const library1property = Symbol('lib1'); function lib1tag(obj) { obj[library1property] = 42; } const library2property = Symbol('lib2'); function lib2tag(obj) { obj[library2property] = 369; }
這方面 symbols 的確對 JavaScript 有用。然后你或許會奇怪,不同的庫進行初始化的時候為什么不使用隨機字符串,或者使用命名空間呢?
const library1property = uuid(); // random approach function lib1tag(obj) { obj[library1property] = 42; } const library2property = 'LIB2-NAMESPACE-id'; // namespaced approach function lib2tag(obj) { obj[library2property] = 369; }
-
你是對的,這種方法確實類似於 symbols 的這一作用,除非兩個庫使用相同的屬性名,那就會有被覆寫的風險。但是仍然有一點細微的不同,字符串是不可變的,而 symbols 可以保證永遠唯一,因此仍然有可能會有人生成重名的字符串。從數學意義上 symbols 提供了一個字符串沒有的優點。
-
機敏的讀者已經發現這兩種方案的效果並不完全相同。我們獨有的屬性名仍然有一個缺點:它們的 key 很容易被找到,尤其是當代碼進行遞歸或者序列化對象,考慮如下的例子:
const library2property = 'LIB2-NAMESPACE-id'; // namespaced function lib2tag(obj) { obj[library2property] = 369; } const user = { name: 'Thomas Hunter II', age: 32 }; lib2tag(user); JSON.stringify(user); // '{"name":"Thomas Hunter II","age":32,"LIB2-NAMESPACE-id":369}'
-
假如我們使用 symbols 作為屬性名,json 的輸出將不會包含 symbols,這是為什么呢?因為 JavaScript 支持 symbols,並不意味着 json 規范也會跟着修改。json 只允許字符串作為 key,JavaScript 並沒有試圖讓 json 輸出 symbols。
轉自:https://blog.csdn.net/weixin_34124939/article/details/91456169