詳解Symbol(自定義值,內置值)


ES6 引入了一種新的原始數據類型 Symbol,表示獨一無二的值。它是
JavaScript 語言的第七種數據類型

Symbol 特點:

1. Symbol 的值是唯一的,用來解決命名沖突的問題,即使參數相同

 1 // 沒有參數的情況
 2 let name1 = Symbol();
 3 let name2 = Symbol();
 4 name1 === name2  // false
 5 name1 === name2 // false
 6 
 7 // 有參數的情況
 8 let name1 = Symbol('flag');
 9 let name2 = Symbol('flag');
10 name1 === name2  // false
11 name1 === name2 // false

2.Symbol 值不能與其他數據進行運算

 - 數學計算:不能轉換為數字
 - 字符串拼接:隱式轉換不可以,但是可以顯示轉換
 - 模板字符串

 

 

 

3) Symbol 定義的對象屬性不參與 for…in/of 遍歷,但是可以使用
Reflect.ownKeys / Object.getOwnPropertySymbols() 來獲取對象的所有鍵名

 1 let sy = Symbol();
 2 let obj = {
 3     name:"zhangsan",
 4     age:21
 5 };
 6 obj[sy] = "symbol";
 7 console.log(obj);   //{name: "zhangsan", age: 21, Symbol(): "symbol"}
 8 
 9 for(let key in obj) {
10     console.log(key);
11 }  //只輸出了name,age
12 
13 
14 
15 Object.getOwnPropertySymbols(obj);     //[Symbol()]
16 Reflect.ownKeys(obj);     //["name", "age", Symbol()]
17 
18 Object.keys(obj);                     //["name", "age"]
19 Object.getOwnPropertyNames(obj)      //["name", "age"]
20 Object.keys(obj)         //["name", "age"]
21 Object.values(obj)         //["zhangsan", 21]
22 JSON.stringify(obj)        //{"name":"zhangsan","age":21}

注: 遇到唯一性的場景時要想到 Symbol

Symbol的方法:

1.Symbol.for()
                作用:用於將描述相同的Symbol變量指向同一個Symbol值,這樣的話,就方便我們通過描述(標識)區分開不同的Symbol了,閱讀起來方便

Symbol.for("foo"); // 創建一個 symbol 並放入 symbol 注冊表中,鍵為 "foo"
Symbol.for("foo"); // 從 symbol 注冊表中讀取鍵為"foo"的 symbol


Symbol.for("bar") === Symbol.for("bar"); // true,證明了上面說的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函數每次都會返回新的一個 symbol


var sym = Symbol.for("mario");
sym.toString(); 
// "Symbol(mario)",mario 既是該 symbol 在 symbol 注冊表中的鍵名,又是該 symbol 自身的描述字符串

Symbol()和Symbol.for()的相同點:

  • 它們定義的值類型都為"symbol";

Symbol()和Symbol.for()的不同點:

  • Symbol()定義的值不放入全局 symbol 注冊表中,每次都是新建,即使描述相同值也不相等;

  • 用 Symbol.for() 方法創建的 symbol 會被放入一個全局 symbol 注冊表中。Symbol.for() 並不是每次都會創建一個新的 symbol,它會首先檢查給定的 key 是否已經在注冊表中了。假如是,則會直接返回上次存儲的那個。否則,它會再新建一個。

2.Symbol.keyFor()
                作用: 方法用來獲取 symbol 注冊表中與某個 symbol 關聯的鍵。如果全局注冊表中查找到該symbol,則返回該symbol的key值,形式為string。如果symbol未在注冊表中,返回undefined

// 創建一個 symbol 並放入 Symbol 注冊表,key 為 "foo"
var globalSym = Symbol.for("foo"); 
Symbol.keyFor(globalSym); // "foo"

// 創建一個 symbol,但不放入 symbol 注冊表中
var localSym = Symbol(); 
Symbol.keyFor(localSym); // undefined,所以是找不到 key 的

Symbol的屬性

Symbol.prototype.description
                description 是一個只讀屬性,它會返回 Symbol 對象的可選描述的字符串。

 1 // Symbol()定義的數據
 2 let a = Symbol("acc");
 3 a.description  // "acc"
 4 Symbol.keyFor(a);  // undefined
 5 
 6 // Symbol.for()定義的數據
 7 let a1 = Symbol.for("acc");
 8 a1.description  // "acc"
 9 Symbol.keyFor(a1);  // acc
10 
11 // 未指定描述的數據
12 let a2 = Symbol();
13 a2.description  // undefined
14 
15 
16 Symbol('desc').toString();   // "Symbol(desc)"
17 Symbol('desc').description;  // "desc"
18 Symbol('').description;      // ""
19 Symbol().description;        // undefined

description屬性和Symbol.keyFor()方法的區別是:

  • description能返回所有Symbol類型數據的描述,而Symbol.keyFor()只能返回Symbol.for()在全局注冊過的描述

以上就是Symbol的基本用法,你以為這就完了嗎?上面的只是開胃菜而已,Symbol真正難的地方在於,它玩的都是底層

內置的Symbol值:

除了定義自己使用的 Symbol 值以外,ES6 還提供了 11 個內置的 Symbol 值,指向語言內部使用的方法。可以稱這些方法為魔術方法,因為它們會在特定的場景下自動執行。

 

內置Symbol的值 調用時機
Symbol.hasInstance 當其他對象使用 instanceof 運算符,判斷是否為該對象的實例時,會調用這個方法
Symbol.isConcatSpreadable 對象的 Symbol.isConcatSpreadable 屬性等於的是一個布爾值,表示該對象用於 Array.prototype.concat()時,是否可以展開。
Symbol.species 創建衍生對象時,會使用該屬性
Symbol.match 當執行 str.match(myObject) 時,如果該屬性存在,會調用它,返回該方法的返回值。
Symbol.replace 當該對象被 str.replace(myObject)方法調用時,會返回該方法的返回值。
Symbol.search 當該對象被 str. search (myObject)方法調用時,會返回該方法的返回值。
Symbol.split 當該對象被 str. split (myObject)方法調用時,會返回該方法的返回值。
Symbol.iterator 對象進行 for…of 循環時,會調用 Symbol.iterator 方法,返回該對象的默認遍歷器
Symbol.toPrimitive 該對象被轉為原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。
Symbol. toStringTag 在該對象上面調用 toString 方法時,返回該方法的返回值
Symbol. unscopables 該對象指定了使用 with 關鍵字時,哪些屬性會被 with環境排除。

特別的: Symbol內置值的使用,都是作為某個對象類型的屬性去使用

內置值的應用:

Symbol.hasInstance:

對象的Symbol.hasInstance屬性,指向一個內部方法,當其他對象使用instanceof運算符,判斷是否為該對象的實例時,會調用這個方法

 1         class Person {}
 2         let p1 = new Person;
 3         console.log(p1 instanceof Person); //true
 4         // instanceof  和  [Symbol.hasInstance]  是等價的
 5         console.log(Person[Symbol.hasInstance](p1)); //true
 6         console.log(Object[Symbol.hasInstance]({})); //true
 7 
 8 
 9         //Symbol內置值得使用,都是作為某個對象類型的屬性去使用
10         class Person {
11             static[Symbol.hasInstance](params) {
12                 console.log(params)
13                 console.log("有人用我來檢測類型了")
14                 //可以自己控制 instanceof 檢測的結果
15                 return true
16                 //return false
17             }
18         }
19         let o = {}
20         console.log(o instanceof Person)    //重寫為true

Symbol.isConcatSpreadable

值為布爾值,表示該對象用於Array.prototype.concat()時,是否可以展開

1         let arr = [1, 2, 24, 23]
2         let arr2 = [42, 25, 24, 235]
3         //控制arr2是否可以展開
4         arr2[Symbol.isConcatSpreadable] = false
5         console.log(arr.concat(arr2)) //(5)[1, 2, 24, 23, Array(4)]

 

Symbol.iterator

ES6 創造了一種新的遍歷命令 for...of 循環,Iterator 接口主要供 for...of 消費,這個內置值是比較常見的,也是一個對象可以被for of被迭代的原因,我們可以查看對象是否存在這個Symbol.iterator值,判斷是否可被for of迭代,擁有此屬性的對象被譽為可被迭代的對象可以使用for…of循環
在這里插入圖片描述
打印{}對象可以發現,不存在Symbol.iterator,所以{}對象是無法被for of迭代的,而[]數組是可以,因為數組上面有Symbol.iterator屬性
在這里插入圖片描述

小提示:原生具備 iterator 接口的數據(可用 for of 遍歷)

  • Array
  • Arguments
  • Set
  • Map
  • String
  • TypedArray
  • NodeList

那么知道原理了,我們就可以手動的給{}對象加上Symbol.iterator屬性,使其可以被for of 遍歷出來

 1     // 讓對象變為可迭代的值,手動加上數組的可迭代方法
 2         let obj = {
 3             0: 'zhangsan',
 4             1: 21,
 5             length: 2,
 6             [Symbol.iterator]: Array.prototype[Symbol.iterator]
 7         };
 8         for(let item of obj) {
 9             console.log(item);
10         }

這里有個缺陷: 因為我們使用的是數組原型上的Symbol.iterator,所以對象必須是個偽數組才能遍歷,自定義一個對象上的Symbol.iterator屬性,使其更加通用

 1         let arr = [1, 52, 5, 14, 23, 2]
 2         let iterator = arr[Symbol.iterator]()
 3         console.log(iterator.next())  //{value: 1, done: false}        
 4         console.log(iterator.next())  //{value: 52, done: false}       
 5         console.log(iterator.next())  //{value: 5, done: false}    
 6         console.log(iterator.next())  //{value: 14, done: false}       
 7         console.log(iterator.next())  //{value: 23, done: false}       
 8         console.log(iterator.next())  //{value: 2, done: false}        
 9         console.log(iterator.next())  //{value: undefined, done: true}
10         
11         
12 
13         //自定義[Symbol.iterator],使得對象可以通過for of 遍歷
14         let obj = {
15             name: "Ges",
16             age: 21,
17             hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
18             [Symbol.iterator]() { 
19                 console.log(this)
20                 let index = 0
21                 let Keyarr = Object.keys(this)
22                 let len = Keyarr.length
23                 return {
24                     next: () => {
25                         if(index >= len) return {
26                             value: undefined,
27                             done: true
28                         }
29                         let result = {
30                             value: this[Keyarr[index]],
31                             done: false
32                         }
33                         index++
34                         return result
35                     }
36                 }
37             }
38         }
39 
40         for(let item of obj) {
41             console.log(item)
42         }

使用generator和yield簡化

 1         //簡潔版
 2         let obj = {
 3             name: "Ges",
 4             age: 21,
 5             hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
 6             *[Symbol.iterator]() {
 7                 for(let arg of Object.values(this)) {
 8                     yield arg;
 9                 }
10             }
11 
12         }
13 
14         for(let item of obj) {
15             console.log(item)
16         }

這樣實現后,{}對象 就變得可以使用for of遍歷了,當然如果掛載到Obejct.prototype上所以對象都可以使用for of 遍歷了
注: 需要自定義遍歷數據的時候,要想到迭代器。

 

Symbol.toPrimitive

該對象被轉為原始類型的值時,會調用這個方法,返回該對象對應的原始類型值

 1 /*
 2  * 對象數據類型進行轉換:
 3  * 1. 調用obj[Symbol.toPrimitive](hint),前提是存在
 4  * 2. 否則,如果 hint 是 "string" —— 嘗試 obj.toString() 和 obj.valueOf()
 5  * 3. 否則,如果 hint 是 "number" 或 "default" —— 嘗試 obj.valueOf() 和 obj.toString()
 6  */
 7 let a = {
 8     value: 0,
 9     [Symbol.toPrimitive](hint) {
10         switch (hint) {
11             case 'number': //此時需要轉換成數值 例如:數學運算`
12                 return ++this.value;
13             case 'string': // 此時需要轉換成字符串 例如:字符串拼接
14                 return String(this.value);
15             case 'default': //此時可以轉換成數值或字符串 例如:==比較
16                 return ++this.value;
17         }
18     }
19 };
20 if (a == 1 && a == 2 && a == 3) {
21     console.log('OK');
22 }

當然自定義一個valueOf/toString 都是可以的,數據類型進行轉換時,調用優先級最高的還是Symbol.toPrimitive

 1         //存在[Symbol.toPrimitive] 屬性,優先調用
 2         let a = {
 3             value: 0,
 4             [Symbol.toPrimitive](hint) {
 5                 console.log(hint)
 6                 switch(hint) {
 7                     case 'number': //此時需要轉換成數值 例如:數學運算時觸發
 8                         return ++this.value;
 9                     case 'string': // 此時需要轉換成字符串 例如:字符串拼接時觸發
10                         return String(this.value);
11                     case 'default': //此時可以轉換成數值或字符串 例如:==比較時觸發
12                         return ++this.value;
13                 }
14             },
15             valueOf: function() {
16                 console.log("valueOf")
17                 return a.i++;
18             },
19             toString: function() {
20                 console.log("toString")
21                 return a.i++;
22             }
23         };

Symbol.toStringTag

在該對象上面調用Object.prototype.toString方法時,如果這個屬性存在,它的返回值會出現在toString方法返回的字符串之中,表示對象的類型

1 class Person {
2     get [Symbol.toStringTag]() {
3         return 'Person';
4     }
5 }
6 let p1 = new Person;
7 console.log(Object.prototype.toString.call(p1)); //"[object Person]"

上述只說了五個常見的Symobl內置值的使用,剩下的就不一一敘述了,調用時機的清單表已經列出來了,感興趣的可以自己去嘗試研究下

總結:

總的來說Symbol用的最多的地方,還是它作為一個唯一值去使用,但我們需要知道,它不僅僅只是代表一個唯一值,Symbol難的地方在於它的內置值,它玩的都是底層


免責聲明!

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



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