解读一波ES6-Symbol
今天学习时看到了Symbol这个陌生的词,好奇之余,去了解了下它。
symbol是Es6的一种新的原始数据类型。中文翻译是"象征",在应用中表示"独一无二的值"。让我们一起来好好认识一下它吧!(以下代码都是本人在vscode中验证过的哦,保准正确)
1. Symbol为什么会出现?
为了解决对象的属性名冲突问题。ES5的对象中属性名都是字符串,当你使用别人提供的对象,或者别人使用你提供的对象,很容易发生属性名被改写或者被覆盖的问题。而使用Symbol可以完美解决,因为它本质上是一种唯一标识符,可以用来作为对象的唯一属性名。
let a = {
sym:'hi'
}
// 第一种定义的方法
let sym = Symbol()
a[sym] = 'hello';
console.log(a.sym); //输出结果:hi
console.log(a[sym]); //输出结果:hello
注意: Symbol值在作为对象的属性名时,不能使用.点运算符来进行访问。因为点运算符后面总是跟着字符串,若是用点运算符,则在运行时不会去读取sym作为标识名所指代的那个值,而是读取sym字符串。所以当在对象内部使用Symbol值定义一个属性时,Symbol必须放在方括号之中来进行访问。
// 第二种定义方法
let bol = Symbol();
let b = {
[bol]:'hello'
}
上面已经有两种Symbol作为对象属性名的写法,那就顺便把第三种一起说了吧,通过Object.defineOroperty(obj, prop, desc)
其中obj是需要定义属性的对象(比如上面的 b),prop是当前需要定义的属性名,desc是属性描述符。
//第三种定义方法
let sym = Symbol()
let a = {}
Object.defineProperty(a,sym,{value:'hello'})
console.log(a[sym]); //输出结果:hello
2. Symbol的定义
Symbol值通过Symbol函数生成,可以接受一个字符串作为参数,表示对Symbol值的描述。因为参数为字符串,所以定义时可以为空。而有参数这个设置主要是为了便于Symbol值在控制台显示,或者转为字符串时,能够分清是哪一个值。
下面例子中bol和boll都是Symbol函数的返回值,而且参数相同,但是它们不相等。因为Symbol函数中的参数只是表示对当前Symbol值的描述。
let sym = Symbol();
let bol = Symbol('id');
let boll = Symbol('id');
//查看sym的类型
console.log(typeof sym); //输出结果:symbol
console.log(bol == boll); //输出结果:false
3. 读取Symbol的描述
下面代码中,bol的描述就是字符串id。
读取这个描述需要将Symbol值显式的转化为字符串。举个例子:
let bol = Symbol('id');
console.log( String(bol) ); //输出结果: Symbol(id)
console.log( bol.toString() ); //输出结果:Symbol(id)
但是很明显的上面的方法不怎么方便,所以ES2019提供了一个Symbol的实例属性description,可以直接返回Symbol的描述。继续举例子:
let bol = Symbol('id');
console.log(bol.description); //输出结果:foo
4. Symbol的一些特点
-
Symbol数据类型最主要的特点是唯一性。从上面的例子中可得出。
-
另一个特点是隐藏性。
- 通过
for...in、for...of循环,或者Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()都不能够访问Symbol。举两个例子验证一下:
- 通过
let sym = Symbol()
let a = {
[sym]:'how are yuo'
}
for(let item in a){
console.log(a[item]);
}
// 输出结果为空
let sym = Symbol()
let a = {
[sym]:'how are yuo'
}
for(let item in a){
console.log(a[item]);
}
console.log(Object.keys(a));
//输出结果: []
-
但是它也不是对象的私有属性,通过Object.getOwnPropertySymbols() 方法是可以访问到指定对象的所有Symbol属性名。这个方法返回的是一个数组,数组成员是当前对象的所有用作属性名的Symbol值(如下面例子的sym)。
``` let sym = Symbol() let a = { [sym]:'how are you' } let arr = Object.getOwnPropertySymbols(a); console.log(arr); //输出结果:[Symbol()] console.log(arr[0]); //输出结果:symbol() console.log(a[arr[0]]); //输出结果:how are you ``` -
还可以使用一个新的API,Reflect.ownkeys(),它可以返回对象中所有类型的键名,包括常规键名(字符串)和Symbol键名。上例子:
let sym = Symbol('sym');
let a = {
[sym]:'i',
next:'am',
nextt:'fine'
}
console.log(Reflect.ownKeys(a));
//输出结果:[ 'next', 'nextt', Symbol(sym) ]
很明显,Reflect.ownkeys()方法返回的是一个数组,成员是对象中所有的键名(也就是属性名)。
所以,我们可以利用它隐藏的特性,为对象定义一些非私有的、但又希望只用于内部的方法。
5. Symbol值也可以用作全局数据
ES6中还提供了Symbol.for(),Symbol.keyFor() 两种方法。
Symbol.for()方法与Symbol()一样接受一个字符串作为参数,但是不同的是它被调用的时候,会先检查是否有此参数作为描述的Symbol值,有就返回这个Symbol值,没有时才会新建一个Symbol值,并将其注册到全局。
他们的区别是:
Symbol()没有登记机制,所以每次调用都会返回一个不同的值。
Symbol.for()有登记机制,而且为 Symbol 值登记的名字,是全局环境的,所以可以做到重复调用。
let one = Symbol.for('sym');
let two = Symbol.for('sym');
console.log(one == two);
//输出结果:true
所以,使用Symbol.for()创建的Symbol值,只要作为参数的字符串是相同的,那么Symbol值也是相等的。
Symbol.keyFor()方法返回一个在全局已登记的Symbol类型值的key(就是上面的字符串参数)。
let one = Symbol.for('sym');
console.log(Symbol.keyFor(one));
//输出结果:sym
let two = Symbol('sym');
console.log(Symbol.keyFor(two));
//输出结果:undefined
//原因:two是为登记的Symbol值