詳解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.descriptiondescription 是一個只讀屬性,它會返回 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難的地方在於它的內建值,它玩的都是底層