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難的地方在於它的內置值,它玩的都是底層