(一)、引出symbol
- 在ES5中,对象的属性名都是字符串,这容易造成属性名的冲突(比如,在模块化应用中,如果你使用了一个其他人提供的对象,同时你又想为这个对象添加新的属性,这个新的属性的名字,就有可能和原来的对象中的属性名发生冲突,导致覆盖产生,如下面的例子:)
let a = {
x:1,
y:2
}
a['x'] = 2
console.log(a)//{x:2,y:2}
- 如果存在一种机制,保证每个属性的名字都是独一无二的,这样就能够防止属性的冲突了。这便是ES6引入Symbol的原因了
(二)、Symbol介绍
1、Symbol()
- ES6引入了Symbol,它为原始数据类型,表示独一无二的值
- Symbol通过Symbol函数生成(注意:Symbol为原始数据类型,不能使用new命令)
let s1 = Symbol()
let s2 = Symbol()
console.log(s1===s2)//false
console.log(typeof s1)//'symbol'
2、Symbol()的参数
- Symbol函数也可以接收一个字符串作为参数,表示对Symbol实例的描述,这样做的目的是利于区分:
- 注意:Symbol()的参数只是表示对于当前Symbol值得描述,因此相同参数的Symbol的返回值是不相等的
//s1和s2都是Symbol值,如果不加参数,它们的输出都是Symbol,不利于区分
let s1 = Symbol()
let s2 = Symbol()
console.log(s1)//Symbol
console.log(s2)//Symbol
//参数只是一种描述,相同参数时,两个Symbol()的返回值并不相等
let s3 = Symbol('foo')
let S4 = Symbol('foo')
console.log(s3)//Symbol(foo)
console.log(s4)//Symbol(foo)
console.log(s3 === s4)//false
3、Symbol作为对象中的属性名:
- ES6引入Symbol后,对象中的属性名现在可以有两种,一种是本来就有的字符串,另一种则是新增的Symbol类型。
- 由于每个Symbol值都是不同的,这就意味着,当一个对象由模块构成时,就能够发防止莫一个键被不小心改写或覆盖
let a = {
x:1,
y:2
}
let x = Symbol()
a[x] = 'hello'
console.log(a)
- 注意:Symbol值作为对象属性时,不能使用点运算符,因为点运算符后面接的是字符串。只能使用中括号,因为中括号内接表达式
let a = {}
let x = Symbol()
a[x] = 'hello'
console.log(a[x])//hello
console.log(a.x)//undefined
4、属性名的遍历
- Symbol 作为属性名,该属性不会出现在 for…in 、 for…of 循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 、 JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有Symbol 属性名(以数组形式排列)。
let a = {
x:1,
y:2
}
let x = Symbol()
a[x] = 'hello'
for(key in a){
console.log(key,a[key])
/*
x 1
y 2
*/
}
console.log(Object.getOwnPropertySymbols(a))//[Symbol()]
5、Symbol.for()
- 我们希望重新使用同一个 Symbol 值, Symbol.for 方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1===s2)//true
6、Symbol()和Symbol.for()的区别、以及Symbol.keyfor()
-
Symbol()每次被调用都会生成一个新的Symbol(一位内Symbol(写法没有登记机制,所以每次调用都会返回一个不同的值)
-
Symbol.for()不会每次调用就返回一个新的Symbol,而是会先检查给定的key是否已经存在,如果不存在,则会在全局创建并注册这个key,然后才会新建一个Symbol值
-
Symbol.keyFor()方法返回一个已经登记的Symbol类型值的key
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
7、内置的Symbol值
- 7.1、Symbol.hasInstance
- 该属性可以表示一个方法:[Symbol.hasInstance](),这个方法表示一个构造器对象是否认可一个对象是它的实例。通俗点来说,就是干了instanceof的事情
function Foo(){}
let f = new Foo()
console.log(f instanceof Foo);//true
console.log(Foo[Symbol.hasInstance](f))//true
- 7.2、Symbol.isConcatSpreadable
- 对象的 Symbol.isConcatSpreadable 属性等于一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开。
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
- 7.3、Symbol.match
- 相当于String.prototype.match()方法使用,即用正则表达式去匹配字符串
String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)
- 7.4、Symbol.toPrimitive
- 该属性指向一个方法,该方法表示将对象转换为相应的原始值
- 很多内置操作都会强制将对象转换为原始值,包括字符串,数值和未指定的原始类型
- Symbol.toPrimitive被调用时(即被内置操作强制转换时),会接受一个字符串参数(hint),表示当前运算的模式,一共有三种模式。
-
- Number:该场合需要转成数值
-
- String:该场合需要转成字符串
-
- Default:该场合可以转成数值,也可以转成字符串
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
//内置操作 2*obj,强制将obj转换为数值类型
2 * obj // 246
//内置操作 3+obj, 这种情况,既可以将obj转换为数值也可以转换为字符串
3 + obj // '3default'
obj == 'default' // true
//String(obj),将obj转换为字符串
String(obj) // 'str'