ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。Symbol 值通過Symbol函數生成。
let s = Symbol(); typeof s // "symbol"
在symbol類型出來后,對象擁有了兩種數據類型,一個是字符串,另一個就是symbol數據類型。屬性名是symbol類型的可以保證不會與其他屬性造成沖突
symbol函數可以接受一個字符串作為變量。表示對 Symbol 實例的描述,主要是為了在控制台顯示,或者轉為字符串時,比較容易區分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
這個參數僅僅只是作為我們對symbol這數據的描述,用來區分不同的symbol數據,所以如果我們對Symbol函數傳入了相同的參數,即使他們的描述相等,但是Symbol的返回值是不同的。
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
Symbol 值不能與其他類型的值進行運算,會報錯。但是,Symbol 值可以顯式轉為字符串,也可以轉為布爾值,但是不能轉為數值。即Symbol是可以調用toString方法和作為參數傳入Boolean方法的
但是無法作為參數傳入Number函數轉為數值的。
我們在創建Symbol的時候會給他添加一個描述,ES2019提供了一個實例方法,會直接返回Symbol的描述
const sym = Symbol('foo');
sym.description // "foo"
因為Symbol作為獨一無二的值,他往往會被用在對象的屬性中,防止對象的屬性被覆蓋或者改寫。值得注意的是,在Symbol出現之前,我們在對象中所指定的屬性名都是以字符串的形式存在,也僅僅只能以字符串形式存在。ES6出現了新的數據結構map,它與對象相似,但是卻可以以其他的數據類型作為屬性名存在。同樣的ES6出現Symbol類型之后,傳統的對象屬性名稱不僅可以用字符串表示,也可以用Symbol表示。
但是請注意,Symbol 值作為對象屬性名時,不能用點運算符。對象讀取屬性常見的方法有兩種
obj.key
obj[key]
使用Symbol類型作為對象屬性的讀取
let mySymbol = Symbol(); // 第一種寫法 let a = {}; a[mySymbol] = 'Hello!';
// 第二種寫法 let a = { [mySymbol]: 'Hello!' };
// 第三種寫法 let a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上寫法都得到同樣結果 a[mySymbol] // "Hello!"
點運算符后面總是字符串,所以不會讀取mySymbol作為標識名所指代的那個值,導致a的屬性名實際上是一個字符串,而不是一個 Symbol 值。代碼如下
const mySymbol = Symbol(); const a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
屬性名的遍歷
Symbol 作為屬性名,遍歷對象的時候,該屬性不會出現在for...in、for...of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
有一個Object.getOwnPropertySymbols()方法,可以獲取指定對象的所有 Symbol 屬性名。該方法返回一個數組,成員是當前對象的所有用Symbol 作屬性名的 。
const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const objectSymbols = Object.getOwnPropertySymbols(obj); objectSymbols // [Symbol(a), Symbol(b)]
另一個新的 API,Reflect.ownKeys()方法可以返回所有類型的鍵名,包括常規鍵名和 Symbol 鍵名。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
因為symbol這一個不能被輕易遍歷到的特性,我們可以將一些非私有但是僅僅用在對象內部的屬性用symbol保存在對象中
使用同一個symbol
有時,我們希望重新使用同一個 Symbol 值,Symbol.for()方法可以做到這一點。它接受一個字符串作為參數,然后搜索有沒有以該參數作為名稱的 Symbol 值。如果有,就返回這個 Symbol 值,否則就新建一個以該字符串為名稱的 Symbol 值,並將其注冊到全局
let s1 = Symbol.for('foo'); let s2 = Symbol.for('foo'); s1 === s2 // true
s1和s2都是 Symbol 值,但是它們都是由同樣參數的Symbol.for方法生成的,所以實際上是同一個值。
Symbol.for()與Symbol()這兩種寫法,都會生成新的 Symbol。它們的區別是,前者會被登記在全局環境中供搜索,后者不會。就像diff算法一樣,Symbol.for()調用返回新的symbol類型的時候會去尋找是否有相同key(也就是symbol的描述)的symbol存在,若不存在則創建新的,若存在則不創建新的symbol。而Symbo()則是調用即創建新的數據類型。我們將symbol.for()這一特性稱為symbol的登記機制。
Symbol.keyFor()方法返回一個已登記的 Symbol 類型值的key。
let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
注意,Symbol.for()為 Symbol 值登記的名字,是全局環境的,不管有沒有在全局環境運行。
實例:模塊的 Singleton 模式(參考阮一峰教程)
Singleton 模式指的是調用一個類,任何時候返回的都是同一個實例。
對於 Node 來說,模塊文件可以看成是一個類。怎么保證每次執行這個模塊文件,返回的都是同一個實例呢?
很容易想到,可以把實例放到頂層對象global。
以下是mod.js的代碼,以供暴露實例以供其他模塊使用
// mod.js function A() { this.foo = 'hello'; } if (!global._foo) { global._foo = new A(); } module.exports = global._foo;
加載上面的mod.js。
const a = require('./mod.js');
console.log(a.foo);//hello
上面代碼中,變量a任何時候加載的都是A的同一個實例。
但是,這里有一個問題,全局變量global._foo是可寫的,任何文件都可以修改。
global._foo = { foo: 'world' };
const a = require('./mod.js');
console.log(a.foo);
上面的代碼,會使得加載mod.js的腳本都失真。
為了防止這種情況出現,我們就可以使用 Symbol.for。
// mod.js const FOO_KEY = Symbol.for('foo'); function A() { this.foo = 'hello'; } if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); } module.exports = global[FOO_KEY];
上面代碼中,可以保證global[FOO_KEY]不會被無意間覆蓋,但還是可以被改寫。
// mod.js const FOO_KEY = Symbol('foo'); // 后面代碼相同 ……
global[Symbol.for('foo')] = { foo: 'world' }; const a = require('./mod.js');
如果鍵名使用Symbol方法生成,那么外部將無法引用這個值,當然也就無法改寫。
// mod.js const FOO_KEY = Symbol('foo'); // 后面代碼相同 ……
上面代碼將導致其他腳本都無法引用FOO_KEY。但這樣也有一個問題,就是如果多次執行這個腳本,每次得到的FOO_KEY都是不一樣的。雖然 Node 會將腳本的執行結果緩存,一般情況下,不會多次執行同一個腳本,但是用戶可以手動清除緩存,所以也不是絕對可靠。
第一次認真的看一篇文檔,便將重要的東西挑出來簡述,symbol是以前都沒接觸過的類型,也是沒使用過的。我感覺symbol的出現,讓JS好像變得越來越嚴謹希望有朝一日JS可以變得越來越強大。
宣傳下自己的網站mishi-blog.com
尚在襁褓中的一個網站。
