未来科技
首发于未来科技
ES6  Symbol

ES6 Symbol

1.什么是Symbol?

在ES6之前只有6种基本数据类型,包括5种原始类型(值本身无法改变)的数据,分别是:Undefined、 Null、Boolean、 Number、String,和一种Object类型的数据。在ES6中又新增了一种新的原始数据类型,那就是Symbol,它表示独一无二的值。

2.如何生成一个Symbol的值?

我们只能够通过使用Symbol相关的函数来生成一个Symbol类型的值。最简单的一种方式如下:

// 方法一 
let s = Symbol();
console.log(s); // Symbol()
typeof s; // "symbol"

我们使用了一个特别的Symbol函数来生成我们需要的值,需要注意的是:因为在函数的前面使用new操作符生成的是一个对象,但是通过Symbol函数生成的是一个基本的数据类型,所以我们不能在Symbol函数的前面使用new操作符;当然如果你坚持使用new操作符的话,它会毫不犹豫的给你抛出一个错误:Uncaught TypeError: Symbol is not a constructor(…),让你在风中凌乱。

我们还可以给Symbol函数传递一个参数,用于对这个Symbol值进行描述,方便我们区分不同的Symbol值,以及调试。

// 方法二
let s1 = Symbol('s1');
let s2 = Symbol('s2');

typeof s1; // "symbol"
typeof s2; // "symbol"

console.log(s1); // Symbol(s1)
console.log(s2); // Symbol(s2)

s1 === s2; // false
s1.toString(); // "Symbol(s1)"
s2.toString(); // "Symbol(s2)"

let s3 = Symbol('s2');
s3 === s2; // false

从上面的代码中,我们可以看出来:(1)我们可以通过给Symbol函数传递一个参数,用来区别不同的Symbol值;给Symbol函数传递了描述参数的Symbol值不论是通过控制台打印,还是调用它自身的toString方法都会显示出来它的描述词,便于区分。(2)就算给Symbol函数传递相同的描述字符串,它生成的Symbol值也是不一样的。只是它们的描述符是一样的。

我们还可以使用另一个生成Symbol值的函数Symbol.for()来生成Symbol类型的值。

// 方法三
let s4 = Symbol.for('s4');
console.log(s4); // Symbol(s4)
s4.toString(); // "Symbol(s4)"

let s5 = Symbol.for('s4');
s5 === s4; // true

Symbol.keyFor(s3); // undefined
Symbol.keyFor(s4); // "s4"

使用Symbol.for(describe)方法会先在全局环境中查找有没有使用describe注册的Symbol值,如果有的话就会返回找到的那个值,如果没有的话就会重新创建一个。这样做的目的使我们可以重复使用之前定义过的Symbol值,或者修改与之相关的一些东西。

说到这里,我们就要提及另一个与Symbol.for()相关的函数Symbol.keyFor(),这个函数是用来查找一个Symbol值的注册信息的,如果你是使用Symbol()函数创建的Symbol值,不论你传不传递参数,那么通过Symbol.keyFor()函数是查找不到它的注册信息的。也就是说,通过Symbol()函数产生的Symbol值都是没有户口的孩子。但是通过Symbol.for()函数产生的Symbol值都是可以查到注册信息的,这些Symbol值都是有户口的,不会被罚钱。

3.Symbol值有什么作用,在那些场合使用?

作为对象的属性名,妈妈再也不用担心我会覆盖掉或者改写一个对象的属性名。在实际的工作中,我们可能会经常使用到别人写的类库,然后因为不知道某个对象的属性名,就可能不小心重写了那个对象的某个属性,导致一些不必要的错误出现。但是有了Symbol类型后,我们可以很容易的避免掉这样的失误。

// 现在我们可以使用字符串或者Symbol值来作为对象的属性了
let a = {};
let b = {};
let c = {};

// @1 第一种方法使用Symbol作为属性名
a[s4] = 1;
// @2 第二种方法使用Symbol作为属性名
b = { [s4]: function(){}}
// @3 第三种方法使用Symbol作为属性名
Object.defineProperty(c, s4, {value: 'hello'});

a[s4]; // 1
b[s4]; // function(){}
c[s4]; // "hello"

a.s4; // undefined

使用Symbol值作为对象的属性名称可以有效地避免属性名的覆盖或者改写,当然你也要付出一点小代价;那就是使用对象的Symbol值作为属性名字的时候,获取相应的属性值需要使用obj[symbol]的方式来获取相应的属性值。不可以使用.运算符来获取相应的属性值。

// a就是上面的那个对象a,我们给它新增加一个字符串属性
a.name = 'hello';

Object.keys(a); // ["name"]
Object.getOwnPropertyNames(a); // ["name"]
for(var i in a) {console.log(i)} // name
for(var i of a) {console.log(i)} // Uncaught TypeError: a[Symbol.iterator] is not a function(…)

使用Symbol值作为对象的属性名,也需要我们注意一点,那就是我们不能够通过使用Object.keys(),Object.getOwnPropertyNames()来获取这些属性名了,当然这些属性名也不会出现在for ...in或者for...of循环中了。

Object.getOwnPropertySymbols(a); // [Symbol(s4)]
// 返回所有键名
Reflect.ownKeys(a); // ["name", Symbol(s4)]

我们可以使用Object.getOwnPropertySymbols()方法获取一个对象上的Symbol值的属性名。但是我们可以使用一个新的API,Reflect.ownKeys()函数返回一个对象的所有键名,包括常规的字符串键名以及使用Symbol值得键名。


作为一些常量来使用,可以有效地避免覆盖。

// 比如一个log()函数,可能有不同级别的输出信息,我们可以定义一组常量来表示
const LOG_LEVEL = {
    NORMAL: Symbol('normal'),
    WARN: Symbol('warn'),
    ERROR: Symbol('error')
}
// log不同级别的信息
log(LOG_LEVEL.NORMAL, '...');
log(LOG_LEVEL.ERROR, '...');

4.ES6内置了许多Symbol值,这里就不多作介绍了,大家可以自行查阅研究。


参考的资料:

Metaprogramming in ES6: Symbols and why they're awesome

ECMAScript 6入门

编辑于 2017-01-08

文章被以下专栏收录