Symbol的特性
大部分人可能都知道 symbol 是ES6 新加入的一种基本数据类型,通过 Symbol() 创建,每个 symbol 的值都不相等。
const symbol = Symbol('foo'); console.log(symbol); // Symbol(foo) 其实调用了 toString() console.log(typeof symbol); // symbol console.log(Symbol('foo') === Symbol('foo')); // false 复制代码
上面这几行代码验证了前面的特性。
但是 Symbol 的其他特性可能还有一部分人不清楚。
Symbol 不能使用 new 操作符实例化, 因为 new 操作符返回的是一个 Symbol 包装的对象,但是 symbol 是一个基本数据类型,所以在语法层面会组织 new 操作符的实例化,抛出TypeError
异常。
如果想要得到一个 Symbol 包装器对象,可以通过 Object 包装器来操作 symbol,例如
const symbolObj = new Symbol('foo') // ❌ TypeError console.log(Object(Symbol('foo'))); // ✅ right 复制代码
全局共享 Symbol,使用 Symbol 创建的 symbol 数据都是独一无二的,通常是用于声明一个独一无二的 key,但是某些情况下我们需要获取 symbol 的时候却不太容易,例如有两个文件,其中一个文件定义了一个对象,其中使用 symbol 声明了一个属性
export const symbol = Symbol('foo'); export const obj = { [symbol]: 1, }; 复制代码
我们要分别导出对象和 symbol key,我们分别导入这两个变量来看下
import { obj, symbol } from './test.js'; console.log(obj); // { [Symbol(foo)]: 1 } console.log(obj[symbol]); // 1 console.log(obj[Symbol('foo')]); // undefined 复制代码
我们无法通过 Symbol(‘foo’)作为索引来获取到原来的值,因为使用 Symbol 创建的 symbol 都是独一无二的。这样我们只能导出 symbol,来作为索引使用,如果变量多了会非常繁杂。
这时我们可以使用 Symbol.for 方法来创建全局共享 Symbol。例如,我们将原有的 symbol 通过 Symbol.for的方式创建
const symbol = Symbol.for('foo'); export const obj = { [symbol]: 1, }; 复制代码
我们现在可以只导出 obj 对象,就可以获取到原本定义的 symbol 属性值,不用再将symbol 全部导出了。因为 Symbol.for 的 description 相同就会被判定为相同
import { obj } from './test.js'; console.log(obj[Symbol.for('foo')]); // 1 console.log(Symbol.for('foo') === Symbol.for('foo')) // true 复制代码
用
Symbol.for()
方法创建的的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for() 并不是每次都会创建一个新的 symbol
,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个。
与之对应的 Symbol.keyFor()
可以通过 symbol 来查找 symbol注册表中的 key 值,如果 symbol 不是通过 Symbol.for 创建的则会返回 undefined
const symbol = Symbol.for('foo'); console.log(Symbol.keyFor(symbol)); // foo 复制代码
值得一提得是用 symbol 注册的对象属性值在遍历时是不可枚举的(JSON.stringify() 也会忽略 symbol),为此我们可以使用专门的 API 来获取
const obj = { a: 1, [Symbol('foo')]: 2 } console.log(obj); // { a: 1, [Symbol(foo)]: 2 } console.log(Object.keys(obj)); // [ 'a' ] console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(foo) ] 复制代码
提前出现的 Symbol
虽然 symbol 在 ES6 才正式加入JS 语法,但是在 ES6 之前 Symbol 的一些方法就被用在了 JS的其他 API 中
- Symbol.iterator:迭代器,提供迭代器的对象,可以被
for … of
迭代; - Symbol.match:用于标识对象是否具有正则表达式的行为,例如String.prototype的 startsWith、endsWidth和includes会检查第一参数是否为正则表达式,如果是会抛出异常,此时可以通过 Symbol.match 来关闭其正则行为
const reg = /foo/ // console.log('foo'.startsWith(reg)); TypeError: First argument to String.prototype.startsWith must not be a regular expression reg[Symbol.match] = false console.log('/foo/'.startsWith(reg)); 复制代码
- Symbol.replace:用于指定字符串替换时调用的方法,例如
class S { constructor(v) { this.value = v } [Symbol.replace](str) { return `${str}/${this.value}/replace` } } const s = new S(1) console.log('foo'.replace(s)); // foo/1/replace 复制代码
- Symbol.search:指定了一个搜索方法,返回一个下表。
class S { [Symbol.search](str) { return 1 } } const s = new S() console.log('foo'.search(s)); // 1 复制代码
- Symbol.split:指定一个分割方法,返回处理后的结果,String.prototype.split会调用它
- ……其他不常用的属性