es6之Symbol

169 阅读3分钟

1、前言

Symbol出生的原因

es5中对象属性只能是字符串的形式,但是要求对象的属性名称必须是唯一,这就和容易出现属性名称冲突的问题。例如引入一个第三方库,你想在第三方库基础上扩展,名称很容易造成冲突。

Symbol简介

es6引入Symbol, 是一个原始类型的值(不是对象),表示独一无二的值,作为js的一种数据类型。(数据类型:Number、Boolean、String、Undefined、Null、Object、Symbol)

typeof Symbol('foo') //'symbol'

注意事项

  • 1、不要使用 new 调用 Symbol
  • 2、相同参数 Symbol不会相等
  • 3、不能与其他值进行计算
  • 4、不能转成数值,但是可以显示的转换字符串或布尔值
var sym = new Symbol() //报错

var sym1 = Symbol();
var sym2 = Symbol();

sym1 === sym2 //false

sym1 + 'hello' //报错

Number(sym1) //报错

sym1.toString() //"Symbol(foo)"
String(sym1) //"Symbol(foo)"

Boolean(sym1) //true
!sym1 //false

2、Symbol的描述

Symbol.prototype.description可以获取描述,为打印控制台可以区分是哪个Symbol

//不传
var sym = Symbol();
sym.description //undefined

//字符串
var sym = Symbol('foo');
sym.description //'foo'

//对象
var sym = Symbol({a:'a'});
sym.description //[object Object]

//数组
var sym = Symbol([1,2]);
sym.description //'1,2'

//函数
var sym = Symbol(function foo(){})
sym2.description //"function foo(){}"

从上面可以看出,描述的值,相当于对对参数进行ToString()操作得到结果

3、Symbol.for()与Symbol.keyFor()

Symbol.for()能够做到使用同一个Symbol。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

var sym1 = Symbol.for('foo');
var sym2 = Symbol.for('foo');

sym1 === sym2 //true

需要注意的是,Symbol.for为Symbol值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

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

var sym = Symbol.for('foo');
Symbol.keyFor(sym) //'foo'

而对于Symbol()定义的值使用Symbol.keyFor()就会的到undefined,因为它是没有登记的

var sym = Symbol('foo');
Symbol.keyFor(sym) //undefined

4、应用

作为常量

const COLOR_RED = Symbol();
const COLOR_GREEN  = Symbol();

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return 'red'
    case COLOR_GREEN:
      return 'green';
    default:
      throw new Error('Undefined color');
    }
}

getComplement(COLOR_RED);

作为属性名称

var mysym = Symbol();

//第一种
var obj = {};
obj[mysym] = 'hello';

//第二种
var obj = {
    [mysym]: 'hello'
};

//第三种
var obj = {};
Object.defineProperty(obj, mysym, {value: 'hello'})

console.log(obj[mysym]) // 'hello'

//定义函数这样简写
var obj = {
    [mysym](arg){
        //todo
    }
};

注意:

  • Symbol 值作为对象属性名时,不能用点运算符。因为点运算符后面总是字符串。
  • Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

属性名遍历

Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。Reflect.ownKeys方法可以返回所有类型的键名。

var a = Symbol('a');
var b = Symbol('b');

var obj = {
    [a]: 'a',
    [b]: 'b'
};
Object.getOwnPropertySymbols(obj); 
//[Symbol(a), Symbol(b)]

Reflect.ownKeys(obj);
//[Symbol(a), Symbol(b)]

Object.getOwnPropertyNames(obj)
//[]

Object.keys(obj)
//[]

JSON.toString(obj)
//"[object JSON]"

将Symbol定义的属性造成了一种非私有的内部方法的效果

全局实例对象

// mod.js
const FOO_KEY = Symbol.for('foo');

function A() {
  this.foo = 'hello';
}

if (!global[FOO_KEY]) {
  global[FOO_KEY] = new A();
}

module.exports = global[FOO_KEY];

可以保证global[FOO_KEY]不会被无意间覆盖,但还是可以被改写

global[Symbol.for('foo')] = { foo: 'world' };

//other.js
const a = require('./mod.js');

其他(Symbol值)