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.keys、for...in、JSON.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。