六维教程

ES6 Symbol

对象的键,正常只能写字符串,比如 { name: '小明' },但字符串有个隐患,不同模块各写各的,一不小心就会撞名。

Symbol 就是为这个而来,它是 ES6 新增的一种原始类型,专门生成独一无二的值,用来当对象的键,谁也不会撞上谁。

基本用法

Symbol() 每次调用都返回一个全新的、独一无二的值:

const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false,永远不相等

它不关心里面传的什么,参数只是给这个 Symbol 起个描述,方便调试时认出来:

const s = Symbol('用户名');
console.log(s.toString()); // Symbol(用户名)

Symbol 能当普通值用吗?

它是原始类型,可以直接放进数组、Set、Map,也可以做运算的变量,就是不能拼字符串(会报错,得用 String(s)s.description 取描述)。这一点知道就行。

拿 Symbol 当对象的键

Symbol 最大的用处就是当对象键,保证不撞名:

const key = Symbol('id');
const user = {
  [key]: 1001,      // 计算属性名,用方括号包住变量
  name: '小明'
};
console.log(user[key]); // 1001

{ [key]: 值 } 里的方括号是”用变量的值当键名”,不是数组。

为什么这样更安全?因为字符串键是公开的,别的代码写 user.name 就能碰到;Symbol 键是私有的,只要没拿到那个 Symbol 变量,就取不到这个值。

两个 Symbol 键描述一样,还是不会撞么?

就算描述写得一模一样,Symbol('a')Symbol('a') 也是两个不同的键,各存各的值:

const a = Symbol('标记');
const b = Symbol('标记');
const obj = { [a]: 1, [b]: 2 };
console.log(obj[a]); // 1
console.log(obj[b]); // 2

Symbol.for():想要全局同一个

普通 Symbol() 每次都是新的。如果你想让”同一描述”全局拿到同一个 Symbol,用 Symbol.for()

const s1 = Symbol.for('主题');
const s2 = Symbol.for('主题');
console.log(s1 === s2); // true,同一个

它和 Symbol() 的区别,就在于 Symbol.for() 会先去全局登记表里找,找到了直接返回,找不到才新建。需要跨模块共享同一个键时用它。

内置的 Symbol

JS 自带一些 Symbol 常量,用来”钩住”内置行为。

最常用的是 Symbol.iterator,它决定一个对象能不能被 for...of 遍历:

const arr = [1, 2, 3];
console.log(arr[Symbol.iterator]); // 一个有 next 方法的函数

给对象挂上 [Symbol.iterator],这个对象就能被 for...of 遍历了。

这一块在讲迭代时(见 ES6 迭代)会展开,这里先知道存在就行。

遍历时看不见 Symbol 键

Object.keysfor...inJSON.stringify 都看不到 Symbol 键,它”隐形”了。想专门找它们,用 Object.getOwnPropertySymbols

const key = Symbol('id');
const obj = { [key]: 1, name: '小明' };

Object.keys(obj);            // ['name'],没有 Symbol 键
Object.getOwnPropertySymbols(obj); // [Symbol(id)],单独拿

这个特性常被库用来藏一些内部元数据,不给外部遍历到。

速查卡片

场景 写法
生成唯一值 Symbol()
生成带描述的值 Symbol('描述')
当对象键 { [key]: 值 }
全局共享同一个 Symbol.for('描述')
拿回所有 Symbol 键 Object.getOwnPropertySymbols(obj)

对象扩展、数组扩展讲完后,Symbol 补上了”对象键安全”这一块。接下来换一批新数据结构,Set 与 Map

上一篇
ES6 运算符扩展
下一篇
ES6 迭代