Symbol

118 阅读3分钟

Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为 Object API 提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。

符号的基本用法

Symbol 值通过Symbol()函数生成。
这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol。

let sym = Symbol();
console.log(typeof sym); // symbol

符号没有字面量语法。
由于 Symbol 值不是对象,所以也不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol()函数不能与 new 关键字一起作为构造函数使用。

let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor

调用 Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');

console.log(genericSymbol == otherGenericSymbol);  // false
console.log(fooSymbol == otherFooSymbol);          // false

使用 Symbol 作为属性

在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

通过方括号结构和Object.defineProperty()方法,将对象的属性名指定为一个 Symbol 值。
注意,Symbol 值作为对象属性名时,不能用点运算符。(因为点运算符后面总是字符串,所以不会读取 mySymbol 作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。)

返回对象实例的常规属性数组:Object.getOwnPropertyNames()

返回对象实例的符号属性数组:Object.getOwnProperty- Symbols()

返回同时包含常规和符号属性描述符的对象:Object.getOwnProperty- Descriptors()

返回两种类型的键:Reflect.ownKeys()

let s1 = Symbol('foo'),
    s2 = Symbol('bar');

let o = {
    [s1]: 'foo val',
    [s2]: 'bar val',
    baz: 'baz val',
    qux: 'qux val'
};

console.log(Object.getOwnPropertySymbols(o));
// [Symbol(foo), Symbol(bar)]

console.log(Object.getOwnPropertyNames(o));
// ["baz", "qux"]

console.log(Object.getOwnPropertyDescriptors(o));
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}

console.log(Reflect.ownKeys(o));
// ["baz", "qux", Symbol(foo), Symbol(bar)]

Symbol 属性名的遍历

Symbol 值作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。