详解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 / OwnPropertySymbols()来获取对象的所有键名
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
9for(let key in obj) {
10 console.log(key);
11 } //只输出了name,age
12
13
14
OwnPropertySymbols(obj); //[Symbol()]
16 Reflect.ownKeys(obj); //["name", "age", Symbol()]
17
18 Object.keys(obj); //["name", "age"]
OwnPropertyNames(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");
// "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 属性等于的是⼀个布尔值,表⽰该对象⽤于 at()时,是否可以展开。Symbol.species创建衍⽣对象时,会使⽤该属性
Symbol.match当执⾏ str.match(myObject) 时,如果该属性存在,会调⽤它,返回该⽅法的返回值。
Symbol.search当该对象被 str. search (myObject)⽅法调⽤时,会返回该⽅法的返回值。
Symbol.split当该对象被 str. split (myObject)⽅法调⽤时,会返回该⽅法的返回值。
Symbol.iterator对象进⾏ for…of 循环时,会调⽤ Symbol.iterator ⽅法,返回该对象的默认遍历器
Symbol. toStringTag在该对象上⾯调⽤ toString ⽅法时,返回该⽅法的返回值
内置Symbol的值调⽤时机
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 检测的结果
15return true
16//return false
17 }
18 }
19 let o = {}
20 console.log(o instanceof Person) //重写为true
Symbol.isConcatSpreadable
值为布尔值,表⽰该对象⽤于at()时,是否可以展开
1 let arr = [1, 2, 24, 23]
2 let arr2 = [42, 25, 24, 235]
3//控制arr2是否可以展开
4 arr2[Symbol.isConcatSpreadable] = false
5 console.at(arr2)) //(5)[1, 2, 24, 23, Array(4)]
Symbol.iterator
ES6 创造了⼀种新的遍历命令 f 循环,Iterator 接⼝主要供 f 消费,这个内置值是⽐较常见的,也是⼀个对象可以被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 };
8for(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.()) //{value: 1, done: false}
4 console.()) //{value: 52, done: false}
5 console.()) //{value: 5, done: false}
6 console.()) //{value: 14, done: false}
7 console.()) //{value: 23, done: false}
8 console.()) //{value: 2, done: false}
9 console.()) //{value: undefined, done: true}
10
11
12
13//⾃定义[Symbol.iterator],使得对象可以通过for of 遍历
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
23return {
24 next: () => {
25if(index >= len) return {
26 value: undefined,
27 done: true
28 }
29 let result = {
30 value: this[Keyarr[index]],
31 done: false
32 }
33 index++
34return result
35 }
36 }
37 }
38 }
39
40for(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]() {
7for(let arg of Object.values(this)) {
8 yield arg;
9 }
10 }
11
12 }
13
14for(let item of obj) {
15 console.log(item)
16 }
这样实现后,{}对象就变得可以使⽤for of遍历了,当然如果挂载到Obejct.prototype上所以对象都可以使⽤for of 遍历了注: 需要⾃定义遍历数据的时候,要想到迭代器。
es6字符串转数组该对象被转为原始类型的值时,会调⽤这个⽅法,返回该对象对应的原始类型值
1/*
2 * 对象数据类型进⾏转换:
3 * 1. 调⽤Primitive](hint),前提是存在
4 * 2. 否则,如果 hint 是 "string" —— 尝试 String() 和 obj.valueOf()
5 * 3. 否则,如果 hint 是 "number" 或 "default" —— 尝试 obj.valueOf() 和 String()
6*/
7 let a = {
8 value: 0,
9 [Primitive](hint) {
10switch (hint) {
11case 'number': //此时需要转换成数值例如:数学运算`
12return ++this.value;
13case 'string': // 此时需要转换成字符串例如:字符串拼接
14return String(this.value);
15case 'default': //此时可以转换成数值或字符串例如:==⽐较
16return ++this.value;
17 }
18 }
19 };
20if (a == 1 && a == 2 && a == 3) {
21 console.log('OK');
22 }
当然⾃定义⼀个valueOf/toString都是可以的,数据类型进⾏转换时,调⽤优先级最⾼的还是Primitive 1//存在[Primitive] 属性,优先调⽤
2 let a = {
3 value: 0,
4 [Primitive](hint) {
5 console.log(hint)
6switch(hint) {
7case 'number': //此时需要转换成数值例如:数学运算时触发
8return ++this.value;
9case 'string': // 此时需要转换成字符串例如:字符串拼接时触发
10return String(this.value);
11case 'default': //此时可以转换成数值或字符串例如:==⽐较时触发
12return ++this.value;
14 },
15 valueOf: function() {
16 console.log("valueOf")
17return a.i++;
18 },
19 toString: function() {
20 console.log("toString")
21return a.i++;
22 }
23 };
在该对象上⾯调⽤String⽅法时,如果这个属性存在,它的返回值会出现在toString⽅法返回的字符串之中,表⽰对象的类型
1 class Person {
2 get [StringTag]() {
3return 'Person';
4 }
5 }
6 let p1 = new Person;
7 console.log(String.call(p1)); //"[object Person]"
上述只说了五个常见的Symobl内置值的使⽤,剩下的就不⼀⼀叙述了,调⽤时机的清单表已经列出来了,感兴趣的可以⾃⼰去尝试研究下
总结:
总的来说Symbol⽤的最多的地⽅,还是它作为⼀个唯⼀值去使⽤,但我们需要知道,它不仅仅只是代表⼀个唯⼀值,Symbol难的地⽅在于它的内置值,它玩的都是底层
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论