1、概述

ES5的对象属性名都是字符串,这容易造成属性名的冲突。如果有一种机制,保证每一个属性的名字都是独一无二的就好了,这就是ES6引入symbol的原因。ES6引入了一种新的数据类型Symbol,表示独一无二。他是javascript语法的第七种数据结构,前六种:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)

  1. let s=Symbol();
  2. typeof s;// 'symbol'

注意⚠️:Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始值,不是对象,不能添加属性.Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述

  1. let s1=Symbol('foo');
  2. let s2=Symbol('bar');
  3. s1 //Symbol(foo)
  4. s2 //Symbol(bar)
  5. s1.toString() //"Symbol(foo)"
  6. s2.toString() //"Symbol(bar)"

Symbol函数的参数只是表示对当前Symbol值的描述,因此相同的参数的Symbol函数的返回值是不相等的。

  1. //没有参数的情况
  2. let s1=Symbol();
  3. let s2=Symbol();
  4. s1===s2 //false
  5. //有参数的情况
  6. let s1=Symbol('foo');
  7. let s2=Symbol('foo');
  8. s1===s2 //false

Symbol值不能与其他类型的值进行运算,会报错

  1. let sym=Symbol('My symbol');
  2. "your symbol is "+sym
  3. // TypeError: can't convert symbol to string

但是,Symbol值可以显示转化为字符串。

  1. let sym=Symbol("My symbol");
  2. String(sym) //'Symbol(My symbol)'
  3. sym.toString() //'Symbol(My symbol)'

另外,Symbol值也可以转为布尔值,但是不能转为数值

  1. let sym=Symbol();
  2. Boolean(sym); //true
  3. !sym //false
  4. if(sym) {...}
  5. Number(sym) // TypeError
  6. sym + 2 // TypeError

2、Symbol.prototype.description

ES2019提供了一个实例属性description,直接返回Symbol的描述,用于对象的属性名,就能保证不会出现同名的属性。

  1. const sym=Symbol('foo');
  2. sym.description //foo

3、作为属性名的Symbol

由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符

  1. let mySymbol=Symbol();
  2. //第一种写法
  3. let a={};
  4. a[mySymbol]="Hello";
  5. //第二种写法
  6. let a={
  7. [mySymbol]:'Hello'
  8. }
  9. //第三种写法
  10. let a={};
  11. Object.defineProperty(a,mySymbol,{
  12. value:'Hello'
  13. })
  14. //以上写法都是同样的结果
  15. a[mySymbol] //'Hello'

注意,Symbol值作为对象属性名时,不能用点运算符

  1. const mySymbol=Symbol();
  2. const a={};
  3. a.mySymbol='Hello'
  4. a[mySymbol] //undefined
  5. a['mySymbol'] //"Hello"

因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。同理,在对象的内部,使用Symbol值定义的属性时,Symbol值必须放在方括号之中。

  1. let s=Symbol();
  2. let obj={
  3. [s]: function (arg) {...}
  4. };
  5. obj[s](123);
  6. //采用增强的对象写法,上面代码的obj对象可以写的更简洁一些。
  7. let obj={
  8. [s](arg) {...}
  9. }

Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

  1. const log={};
  2. log.levels={
  3. DEBUG: Symbol('debug'),
  4. INFO: Symbol('info'),
  5. WARN: Symbol('wran')
  6. }
  7. const COLOR_RED=Symbol();
  8. const COLOR_GREEN = Symbol();
  9. function getComplement(color) {
  10. switch (color) {
  11. case COLOR_RED:
  12. return COLOR_GREEN;
  13. case COLOR_GREEN:
  14. return COLOR_RED;
  15. default:
  16. throw new Error('Undefined color');
  17. }
  18. }

4、实例:消除魔术字符串

魔术字符串指的是,在代码之中多次出现,与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改进含义清晰的变量代替。

5、属性名的遍历

Symbol作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnpropertyName()、JSON.stringify()返回
但是,它有不是私有属性,有一个object.getOwnPropertySymbols()方法,可以获取指定对象的所有Symbol属性名。该方法返回一个数组,成员是当前对象的所有用作属性的Symbol值。

  1. const obj={};
  2. let a=Symbol('a');
  3. let b=Symbol('b');
  4. obj[a]='Hello';
  5. obj[b]='World';
  6. const objectSymbols=Object.getOwnPropertySymbol(obj);
  7. objectSymbols //[Symbol(a), Symbol(b)]

另一个新的 API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

  1. let obj={
  2. [Symbol('my_key')]: 1,
  3. enum: 2,
  4. nonEnum: 3
  5. }
  6. Reflect.ownKeys(obj);
  7. // ["enum", "nonEnum", Symbol(my_key)]

6、Symbol.for(),Symbol.keyFor()

  1. let s1=Symbol.for('foo');
  2. let s2=Symbol.for('foo');
  3. s1===s2; //true

Symbol.keyFor()方法返回一个已登记的Symbol类型的key。

  1. let s1=Symbol.for("foo");
  2. Symbol.keyFor(s1) //"foo"
  3. let s2 = Symbol("foo");
  4. Symbol.keyFor(s2) // undefined s2为未登记的Symbol值,所以返回undefined

7、实例:模块的Singleton模式

8、内置的Symbol值

Symbol.hasInstance
Symbol.inConcatSpreadable
Symbol.species
Symbol.match
Symbol.replace
Symbol.search
Symbol.split
Symbol.iterator