symbol


ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:undefinednull、布爾值(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...infor...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

s1s2都是 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

尚在襁褓中的一個網站。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM