背景

JavaScript中有两种数据类型:基本数据类型和对象(对象也包括函数),基本数据类型包括简单数据类型,比如Number(从整数到浮点数,从Infinity到NaN都属于Number类型)、Boolean、String、undefined、null(注意尽管typeof null === object,null仍然是一个基本数据类型)。

基本数据类型的值是不可以改变的,即不能更改变量的原始值,当然可以重新对变量进行赋值。例如,代码:

let x = 1;
x++;

虽然,你通过重新赋值改变了变量x的值,但是变量的原始值1仍没有被改变。

一些语言,比如C语言,有按引用传递和按值传递的概念。JavaScript也有类似的概念,它是根据传递数据的类型推断出来的。如果将值传入一个函数,则在函数中重新对它赋值不会修改它在调用位置的值。但是,如果你修改的是基本数据的值,那么修改后的值会在调用它的地方被修改。

考虑下面的例子:

function primitiveMutator(val) {
val = val + 1;
}
let x = 1;
primitiveMutator(x);
console.log(x); //1
function objectMutator(val) {
val.prop = val.prop + 1;
}
let obj = {prop: 1};
objectMutator(obj);
console.log(obj.prop); //2

基本数据类型(NaN除外)总是与另一个具有相同值的基本数据类型完全相等。如下:

const first = "abc" + "def";
const second = "ab" + "cd" + "ef";

console.log(first === second); //true

然而,构造两个值相同的非基本数据类型则得到不相等的结果。我们可以看到发生了什么:

const obj1 = { name: "666"};
const obj2 = {name: "666"};
console.log(obj1 === obj2); //false

对象在JavaScript中扮演着重要的角色,几乎所有地方可以见到它们的身影。对象通常是键值对的集合,然而这种形式的最大限制是:对象的键只能是字符串,知道Symbol出现这一限制才得到解决。如果我们使用非字符串的值作为对象的键,该值会被强制转换为字符串。下面的程序中可以看到这种强制转换:

const obj = {};
obj.foo = "foo";
obj['bar'] = 'bar';
obj[2] = 2;
obj[{}] = 'someobj';

console.log(obj);

[译] JavaScript 中为什么会有 Symbol 类型?_javascript

Symbol是什么?

现在既然我们已经知道了基本数据类型是什么,也就终于可以定义Symbol了。

Symbol是不能被重新创建的基本数据类型。在这种情况下,Symbol类似与对象,因为对象创建多个实例也将导致不完全相等的值。但是,Symbol也是基本数据类型,因为它不能被改变。下面是Symbol用法的一个例子:

const s1 = Symbol();
const s2 = Symbol();

console.log(s1 === s2); //false

当实例化一个Symbol值时,有一个可选的首选参数,你可以赋值一个字符串。此值用于调试代码,不会真正影响Symbol本身。

const s1 = Symbol('debug');
const str = 'debug';
const s2 = Symbol('xxxy');

console.log(s1 === str); //false
console.log(s1 === s2); //false
console.log(s1); //Symbol(debug)

Symbol作为对象属性

symbol还有一个重要的用法,他们可以被当作对象中的键!下面是一个在对象中使用Symbol作为键的例子:

const obj = {};
const sym = Symbol();
obj[sym] = 'foo';
obj.bar = 'bar';

console.log(obj);
console.log(sym in obj);
console.log(obj[sym]);
console.log(Object.keys(obj));

[译] JavaScript 中为什么会有 Symbol 类型?_基本数据类型_02

注意,Symbol键不会被在Object.keys()返回。也是为了满足向后兼容性。旧版本的JavaScript中没有Symbol数据类型,因此不应该从旧的Object.keys()方法中被返回。

乍一看,这就像是可以用Symbol在对象上创建私有属性!许多其他编程语言可以在其类中有私有属性,而JavaScript却遗漏了这种功能,长期以来被视为其语法的一种缺点。

不幸的是,与该对象交互的代码仍然可以访问对象那些键为Symbol的属性。甚至是在调用代码自己无法访问Symbol的情况下也有可能发生。例如,Reflect.ownKeys()方法能够得到一个对象的所有键的列表,包括字符串和Symbol:

function tryToAddPrivate(obj) {
obj[Symbol('Pseudo Private')] = 42;
}

const obj = { prop: 'hello' };
tryToAddPrivate(obj);

console.log(Reflect.ownKeys(obj));

console.log(obj[Reflect.ownKeys(obj)[1]]); // 42