ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是
JavaScript 语言的第七种数据类型
Symbol 特点:
1. Symbol 的值是唯一的
,用来解决命名冲突的问题,即使参数相同
// 没有参数的情况
let name1 = Symbol();
let name2 = Symbol();
name1 === name2 // false
name1 === name2 // false
// 有参数的情况
let name1 = Symbol('flag');
let name2 = Symbol('flag');
name1 === name2 // false
name1 === name2 // false
2.Symbol 值不能与其他数据进行运算
- 数学计算:不能转换为数字
- 字符串拼接:隐式转换不可以,但是可以显示转换
- 模板字符串
3) Symbol 定义的对象属性不参与 for…in/of 遍历,但是可以使用
Reflect.ownKeys / Object.getOwnPropertySymbols()
来获取对象的所有键名
let sy = Symbol();
let obj = {
name:"zhangsan",
age:21
};
obj[sy] = "symbol";
console.log(obj); //{name: "zhangsan", age: 21, Symbol(): "symbol"}
for(let key in obj) {
console.log(key);
} //只输出了name,age
Object.getOwnPropertySymbols(obj); //[Symbol()]
Reflect.ownKeys(obj); //["name", "age", Symbol()]
Object.keys(obj); //["name", "age"]
Object.getOwnPropertyNames(obj) //["name", "age"]
Object.keys(obj) //["name", "age"]
Object.values(obj) //["zhangsan", 21]
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 对象的可选描述的字符串。
// Symbol()定义的数据
let a = Symbol("acc");
a.description // "acc"
Symbol.keyFor(a); // undefined
// Symbol.for()定义的数据
let a1 = Symbol.for("acc");
a1.description // "acc"
Symbol.keyFor(a1); // acc
// 未指定描述的数据
let a2 = Symbol();
a2.description // undefined
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
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运算符,判断是否为该对象的实例时,会调用这个方法
class Person {
}
let p1 = new Person;
console.log(p1 instanceof Person); //true
// instanceof 和 [Symbol.hasInstance] 是等价的
console.log(Person[Symbol.hasInstance](p1)); //true
console.log(Object[Symbol.hasInstance]({
})); //true
//Symbol内置值得使用,都是作为某个对象类型的属性去使用
class Person {
static[Symbol.hasInstance](params) {
console.log(params)
console.log("有人用我来检测类型了")
//可以自己控制 instanceof 检测的结果
return true
//return false
}
}
let o = {
}
console.log(o instanceof Person) //重写为true
Symbol.isConcatSpreadable
值为布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开
let arr = [1, 2, 24, 23]
let arr2 = [42, 25, 24, 235]
//控制arr2是否可以展开
arr2[Symbol.isConcatSpreadable] = false
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
遍历出来
// 让对象变为可迭代的值,手动加上数组的可迭代方法
let obj = {
0: 'zhangsan',
1: 21,
length: 2,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for(let item of obj) {
console.log(item);
}
这里有个缺陷: 因为我们使用的是数组
原型上的Symbol.iterator
,所以对象必须是个伪数组才能遍历,自定义一个对象上的Symbol.iterator
属性,使其更加通用
let arr = [1, 52, 5, 14, 23, 2]
let iterator = arr[Symbol.iterator]()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 52, done: false}
console.log(iterator.next()) //{value: 5, done: false}
console.log(iterator.next()) //{value: 14, done: false}
console.log(iterator.next()) //{value: 23, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
//自定义[Symbol.iterator],使得对象可以通过for of 遍历
let obj = {
name: "Ges",
age: 21,
hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
[Symbol.iterator]() {
console.log(this)
let index = 0
let Keyarr = Object.keys(this)
let len = Keyarr.length
return {
next: () => {
if(index >= len) return {
value: undefined,
done: true
}
let result = {
value: this[Keyarr[index]],
done: false
}
index++
return result
}
}
}
}
for(let item of obj) {
console.log(item)
}
使用generator和yield简化
//简洁版
let obj = {
name: "Ges",
age: 21,
hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
*[Symbol.iterator]() {
for(let arg of Object.values(this)) {
yield arg;
}
}
}
for(let item of obj) {
console.log(item)
}
这样实现后,{}对象
就变得可以使用for of
遍历了,当然如果挂载到Obejct.prototype上所以对象都可以使用for of 遍历了
注: 需要自定义遍历数据的时候,要想到迭代器。
Symbol.toPrimitive
该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值
/*
* 对象数据类型进行转换:
* 1. 调用obj[Symbol.toPrimitive](hint),前提是存在
* 2. 否则,如果 hint 是 "string" —— 尝试 obj.toString() 和 obj.valueOf()
* 3. 否则,如果 hint 是 "number" 或 "default" —— 尝试 obj.valueOf() 和 obj.toString()
*/
let a = {
value: 0,
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': //此时需要转换成数值 例如:数学运算`
return ++this.value;
case 'string': // 此时需要转换成字符串 例如:字符串拼接
return String(this.value);
case 'default': //此时可以转换成数值或字符串 例如:==比较
return ++this.value;
}
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
当然自定义一个valueOf/toString
都是可以的,数据类型进行转换时,调用优先级最高的还是Symbol.toPrimitive
//存在[Symbol.toPrimitive] 属性,优先调用
let a = {
value: 0,
[Symbol.toPrimitive](hint) {
console.log(hint)
switch(hint) {
case 'number': //此时需要转换成数值 例如:数学运算时触发
return ++this.value;
case 'string': // 此时需要转换成字符串 例如:字符串拼接时触发
return String(this.value);
case 'default': //此时可以转换成数值或字符串 例如:==比较时触发
return ++this.value;
}
},
valueOf: function() {
console.log("valueOf")
return a.i++;
},
toString: function() {
console.log("toString")
return a.i++;
}
};
Symbol.toStringTag
在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型
class Person {
get [Symbol.toStringTag]() {
return 'Person';
}
}
let p1 = new Person;
console.log(Object.prototype.toString.call(p1)); //"[object Person]"
上述只说了五个常见的Symobl内置值的使用,剩下的就不一一叙述了,调用时机的清单表已经列出来了,感兴趣的可以自己去尝试研究下
总结:
总的来说Symbol用的最多的地方,还是它作为一个唯一值去使用
,但我们需要知道,它不仅仅只是代表一个唯一值,Symbol难的地方在于它的内置值,它玩的都是底层