你真的了解 Symbol 吗?

187 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

介绍

Symbol 是 ES6 新引入的第 6 种原始类型,用作非字符串的属性名。新标准将 Symbol 属性与对象中其他属性分别分类。

Symbol 出现以前,无论属性名由什么元素构成,全部通过一个字符串类型的名称来访问。ES6 和之后版本 Symbol 可以为属性添加非字符串名称。

使用

创建 Symbol

Symbol 类型没有字面量语法,需要通过全局的 Symbol 函数创建一个 Symbol。Symbol 函数接收一个可选参数(文本描述),增强代码可读性和便于调试 Symbol。

const firstName = Symbol("first name");
const person = {};
person[firstName] = "Zhao";

console.log("first name" in person); // false
console.log(person[firstName]); // "Zhao"
console.log(firstName); // "Symbol(first name)"

Symbol 的描述被存储在内部的 [[Description]] 属性中,只有当调用 Symbol 的 toString() 方法时才可以读取到。执行 console.log() 时隐时的调用了 firstName 的 toString() 方法。

注:不要对 Symbol 函数使用 new,它并不是一个构造器,也不会创建一个对象。

typof 也支持返回 “symbol”,可以用 typeof 来检测变量是否为 Symbol 类型。

console.log(typeof firstName) // “symbol”

Symbol 是一个函数对象,即使每次传入的参数都一样,也永远不会返回相同的值。Symbol 的主要意义是创建一个类字符串的不会与其他任何值冲突的值。 这意味着可以将 Symbol 安全地为对象添加新属性,而无须担心可能重写已有的属性。可以使用一个 Symbol 作为事件名的常量。

但是在不同的代码中如何有效的共享这些 Symbol,这就需要 Symbol 共享体系。

Symbol 共享体系

const uid = Symbol("uid");
const uid2 = Symbol("uid");

console.log(uid === uid2); // false

ES6 创建了一个全局 Symbol 注册表,我们想在不同的代码中共享 Symbol,可以使用 Symbol.for() 方法。该方法只接收一个参数,和创建 Symbol 时一样。

const uid = Symbol.for("uid");
const obj = {};
obj[uid] = "12345";

const uid2 = Symbol.for("uid");

console.log(obj[uid]); // "12345"
console.log(obj[uid2]); // "12345"
console.log(uid === uid2); // true

Symbol.for() 方法首先在全局 Symbol 注册表中搜索键为 ”uid” 的 Symbol 是否存在,如果存在就返回已有的 Symbol,否则创建一个新的 Symbol。

上面例子中,第一次调用Symbol.for("uid")会创建这个 Symbol,第二次调用会直接从 Symbol 的全局注册表中检索到这个 Symbol。

还可以使用Symbol.keyFor()在 Symbol 全局注册表中检索与 Symbol 有关的键。如下:

const uid = Symbol("uid");
const uid2 = Symbol("uid");

console.log(Symbol.keyFor(uid)); // "uid"
console.log(Symbol.keyFor(uid2)); // "uid"

well-konw Symbol 暴露内部操作

ES6 中主要通过在原型链上定义与 Symbol 相关的属性来暴露更多的语言内部逻辑。开发者可以通过多种方法修改对象的特性。

比如 Symbol.isConcatSpreadable:

const collection = {
    0: "hello",
    1: "world",
    length: 2,
    [Symbol.isConcatSpreadable]: true,
};

let messages = ["hi"].concat(collection);

console.log(messages.length);
a; // 3
console.log(messages); // ["hi","hello","world"]

Symbol.isConcatSpreadable 属性是一个布尔值,如果是 true,表示对象有 length 和数字键,所以它的数值型属性值应该被独立添加到 concat() 调用的结果中。