在没认识 Symbol 之前,我并不知道它是个什么东西,它对于现在的 javascript 代码有什么作用。在翻阅了网上一些相关文章后,我大概的了解到,它就是俗称的 javascript 中的第七种数据类型。

在ES6中,Symbol属于一种新的原始数据类型,用于表示独一无二的值。

但是通过整理完它的相关内容后,我特么还是不知道这个东西有什么用?

一、创建 Symbols

我们可以通过调用 Symbol 方法来创建 Symbols,像这样:

var a = Symbol();

console.log(a); // Symbol()
console.log(typeof a); // 'symbol'

虽然创建的 Symbols 是原始数据类型,但是以下定义的两个 symbol 并不相等,因为 Symbol 的初衷就是表示独一无二的值:

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

console.log(a, b); // Symbol() Symbol()
console.log(a === b); // false
console.log(a == b); // false

通过上面的代码,我们会发现 a、b 两个 symbol 变量不相等,即便它们两者都是返回 Symbol()。并且正是由于它们返回的值相同,但不相等,这使得我们不太好区分这两个变量。

针对这个问题,Symbol 函数接受一个可选参数,用于描述当前的 symbol 变量,这对于我们区分和调试此类变量特别有用:

var a = Symbol('aa');
var b = Symbol('bbb');
var c = Symbol('aa');

console.log(a, b, c); // Symbol(aa) Symbol(bbb) Symbol(aa)
console.log(a === b); // false
console.log(a == b); // false
console.log(a === c); // false
console.log(a == c); // false

这里值得注意的是,虽然 a、c 两个 symbol 变量中的描述相同,但是它们两者还是不相等。

同时,正因为它是像字符串、数字这样的一类原始数据类型,所以,你不能用这个 Symbol 方法来构建新的实例,否者会出现报错:

var a = new Symbol(); // Symbol is not a constructor

二、使用 Symbols

上面阐述了如何创建 symbol 变量?但我们为什么要创建 symbol 变量 呢?换言之,ES6 中引入它的原因是什么。

在开发一些大程序时,我们常常会引用别人定义的对象,可能也会对这个对象进行方法扩展。但是有的时候,我们定义的扩展对象属性名会在不经意间覆盖原来的同名属性,比如:

var obj = {
input: function() {
console.log('a');
}
};

obj.input = function() {
console.log('aa');
};

obj.input(); // 'aa'

可以看到,obj 对象中原本有 input 方法,但是我们使用同名属性名覆盖了这个方法。当别人再开发这个文件时,obj.input 就不能按如期输出 a ,这显然不是我们想要的。

此时,symbol 便派上用场了。你可以通过如下方式定义同名属性,但是这个新定义的方法却不会覆盖原来的方法:

var obj = {
input: function() {
console.log('a');
}
};

var input = Symbol();

obj[input] = function() {
console.log('aaa');
};

obj.input(); // 'a'
obj[input](); // 'aaa'

除了上面的这种方式,它还有其他两种形式来定义对象中的属性,分别是 对象字面量 或者 defineProperty :

var input1 = Symbol(),
input2 = Symbol();

// 对象字面量
var obj = {
[input1]: function() {
console.log('a1');
}
};

// defineProperty
Object.defineProperty(obj, input2, { value: function() { console.log('a2'); } });

obj[input1](); // 'a1'
obj[input2](); // 'a2'

细心的你可能已经发现,通过 symbol 定义的对象属性,最后的引用或者调用都是采用中括号 [] 的形式。如果你使用点 . 的形式,则会抛出错误:

var input1 = Symbol();

var obj = {
[input1]: function() {
console.log('a1');
}
};

obj.input1(); // obj.input1 is not a function

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

之前我们说了,通过 相同描述 定义的 symbol 变量 也是不等的。但偏偏有的时候,我们需要创建的 symbol 被共享,则我们可以使用 Symbol.for() :

var a1 = Symbol('a');
var a2 = Symbol('a');

console.log(a1 === a2); // false

var b1 = Symbol.for('b');
var b2 = Symbol.for('b');

console.log(b1 === b2); // true

在上面的代码中,每调用 Symbol(‘a’) 一次,则都会新生成一个 symbol 变量。而 Symbol.for(‘b’) 则不同,它调用的时候,首先会查询全局的注册表中是否有包含键 ‘b’ 这样一个 symbol 变量,如果有,则返回这个变量,如果没有,则新生产一个 symbol 变量。

这样一来,如果我们希望更改对象属性,则可以使用 Symbol.for():

var b1 = Symbol.for('b');
var b2 = Symbol.for('b');

var obj = {
[b1]: function() {
console.log('bb');
}
};

obj[b2] = function() {
console.log('cc');
};

obj[b1](); // 'cc'

到目前为止,我们只会通过不同的描述来定义不同的 symbol 变量,但如何反向的获取这个描述呢?答案是:Symbol.keyFor() ,它的参数为 symbol 变量,需要注意的是,这个方法只能获取 Symbol.for() 形式定义的 symbol 变量,如果传入的参数是 Symbol() 定义的变量,则默认返回 undefined :

var a1 = Symbol('a');
var b1 = Symbol.for('b');

console.log(Symbol.keyFor(a1)); // undefined
console.log(Symbol.keyFor(b1)); // 'b'