ES6 的 Symbol 是一种新的 JavaScript 数据类型,它是原始数据类型,专门用来表示独一无二的值。Symbol 是 ES6 针对 JavaScript 缺陷的一种解决方案,用来解决属性名冲突、对象私有属性、对象迭代器等问题。
Symbol 的基本语法是这样
1 | let sym = Symbol("description"); |
这其中,描述符是一个可选的字符串,用于调试和检查。Symbol 主要有三个特点:
- 每个Symbol值都是唯一的,这意味着没有两个不同的Symbol值会相等,即使它们看起来一样也是如此。
- Symbol值不会自动转换为字符串或数字,它们可以被用作对象属性的键。
- Symbol值是不可变的,这意味着一旦创建了一个Symbol,它的属性是不可修改的。
首先,我们需要了解一下 JavaScript 中对象属性的默认特性:可见。如果多个模块定义了同名的属性,就会发生冲突。这时就可以使用 Symbol 提供唯一性的保证,防止属性名冲突。在使用 Symbol 时,不同的属性名不会相等,因此可以防止属性名冲突的问题。下面来看一个例子:
1 | const id1 = Symbol('id'); |
两个 Symbol 类型的变量 id1 和 id2 的描述符都是 ‘id’,但它们不相等,因为它们是独立的值,具有唯一性。
我们来用一个简单的例子,看一下 Symbol 的一些使用场景,比如如何实现对象私有属性。JavaScript 并没有提供一种私有属性的语法,而是通过给属性名加上一个唯一的 Symbol 来实现。这样,属性名由 Symbol 组成,从而无法从外部访问,保证了属性的私有性。
1 | const obj = { |
我们在对象 obj 中使用了 Symbol 实例作为属性名,该属性名由 Symbol 类型的变量 ‘name’ 组成,从而保证了该属性的私有性。
除了提供唯一性的保证和实现私有属性的功能,Symbol 也可以用于实现对象的迭代器,以及在全局注册表中检索和注册 Symbol。这些功能需要详细了解,才能进行灵活的应用。
比如说迭代器,在 ES6 中,要想将对象用于循环迭代,需要定义一个迭代器对象,并将其赋给 Symbol.iterator 属性。可以这样来写:
1 | const obj = { |
在这个例子中,我们定义了一个对象 obj ,并且将一个迭代器对象赋给 Symbol.iterator 属性。这样, obj 就可以用于 for…of 循环,实现了对象的迭代功能。当然,还可以这样写:
1 | const myIterable = {}; |
上面还提到了再全局注册表中检索和注册 Symbol,那么怎么做呢?
在 JavaScript 中,Symbol.for() 和 Symbol.keyFor() 分别用于在全局注册表中检索和注册 Symbol。Symbol.for() 在全局注册表中搜索具有相同描述符的 Symbol,如果存在,则返回该 Symbol;否则,会新建一个具有相同描述符的 Symbol 并返回。而 Symbol.keyFor() 则根据 Symbol 返回其描述符。这两个方法可以用来确保只有一个 Symbol 实例存在。
1 | const firstName = Symbol.for('firstName'); |
在这个例子中,我们使用 Symbol.for() 方法创建了两个 Symbol 实例,它们的描述符都是 ‘firstName’。由于它们描述符相同,因此它们是相等的,返回值为 true。
由于 Symbol 具有唯一性,我们可以使用 Symbol.for() 确保只有一个 Symbol 实例存在,从而保证程序的正确性。
需要注意的是,使用 Symbol.for() 在全局注册表中注册 Symbol 可以使其被多个模块共享,而使用 Symbol() 创建的 Symbol 仅在当前模块中有效。此外,Symbol.for() 会在全局注册表中缓存每个 Symbol 实例,因此应该避免滥用此方法,以免产生内存泄漏。