走过路过别错过的Symbol

446 阅读5分钟

解读一波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值在控制台显示,或者转为字符串时,能够分清是哪一个值。

下面例子中bolboll都是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的一些特点

  1. Symbol数据类型最主要的特点是唯一性。从上面的例子中可得出。

  2. 另一个特点是隐藏性。

    • 通过for...infor...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值