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难的地方在于它的内置值,它玩的都是底层