英雄登场——Symbol拯救ES6的命名冲突

10 阅读5分钟

JavaScript 作为一种广泛使用的编程语言,其灵活性和动态特性使其成为构建复杂应用的理想选择。随着 ECMAScript 2015 (ES6) 的发布,JavaScript 引入了多种新特性以增强其功能和性能。其中,Symbol 类型作为原始数据类型的一员,为开发者提供了一种创建独一无二且不可改变值的方法。本文将详细探讨 Symbol 的特性、用途以及它在大型项目中的重要性,并解释与之相关的几个关键方法。

为什么需要Symbol?

试想一下,在大厂的大型项目中,肯定是多人协作共同开发,每个开发人员在自己负责的部分中创建一个对象:

//开发人员甲
const classMate = {
name:'Mark',
grade:5,
gernder:'male'
};
//开发人员乙
const classMate = {
name:'Alice',
grade:7,
gernder:'female'
};

当他们将项目合并时就发现了一个尴尬的事——命名冲突,在没有事先商量好的情况下使用了相同的变量,这就会导致有个人的classMate变量会被覆盖。会出现这样的问题,是因为classMate属性属于全局变量,当出现同名的变量时就会发生覆盖。这是大型项目在开发中的一大痛点。ES6为了解决该问题,就创造了一个全新的简单数据类型——Symbol

Symbol的诞生,意味着可以同时存在两个用Symbol创建的同名变量,就像两个同名同姓的陌生人。

Symbol的特点

1.唯一性

Symbol是一种唯一标识符,在每次调用Symbol()函数时都会创建全新的Symbol实例,这造就了Symbol的唯一性。即使它们的描述相同,也不会相等。

[Symbol('olivia')]:{grade:8,gender:'female'},
[Symbol('olivia')]:{grade:7,gender:'male'},

这里使用方括号语法,将Symbol('olivia')放在方括号中,直接把变量作为键来访问所存储的属性。因为Symbol是通过变量引用的,而且它们不是静态字符串识别符,要通过这种方式来确保属性名的唯一性和灵活性。

2.不可枚举

Symbol作为对象的键时,默认情况下是不会被寻常的枚举方法(如for...in...Object.keys()Object.values()Object.entries())所列出来,尽管它的emumberable(是否可枚举)为true。这是因为Symbol设计独特,保护私有数据不被意外修改和访问,这也能有效的避免命名冲突。 如果想要遍历获取它们,可以通过Object.getOwnPropertySymbols()来返回包含对象自身所有的Symbol键名的值;Object.getOwnPropertyDescriptors(obj)来返回一个对象,该对象中包含所有的属性描述符,也包括Symbol键名的属性。

const syms = Object.getOwnPropertySymbols(classMates);
console.log(syms);
console.log(Object.getOwnPropertyDescriptors(classMates));

Symbol的作用

1.避免命名冲突

Symbol是独一无二的,即使两个Symbol使用相同的描述来创建,也是两个不相等的对象,这使得Symbol特别适用于在大型项目中避免命名冲突。

const sym1 = Symbol('id');
const sym2 = Symbol('id');
console.log(sym1 === sym2); // false
2.定义私有属性

虽然Symbol不是完全意义上的私有属性,但它们也不会出现在寻常的枚举操作(如for...inObject.keys()等)中,这使得它们适合模拟类或对象中的私有成员,也可以保护代码不被轻易修改。

const _privateKey = Symbol('private');

const obj = {
  [_privateKey]: 'This is a private value',
  publicMethod() {
    console.log(this[_privateKey]);
  }
};

obj.publicMethod(); // 输出: This is a private value
console.log(obj[_privateKey]); // 直接访问是可能的,但不会被常规枚举方法列出
3.全局符号注册表(Global Symbol Registry)

全局符号注册表是JS中的一种机制,允许不同代码模块和文件共享一个Symbol实例。在大型项目中可以确保不同的开发人员能引用相同的Symbol来保持唯一性。

可以通过Symbol.for(key)来创建一个全局符号并添加到全局符号注册表中。该方法可以将一个字符串作为键,并返回关联的Symbol。如果存在相同键的Symbol,则返回现有的Symbol

const sym1 = Symbol.for('sharedKey');
const sym2 = Symbol.for('sharedKey');

console.log(sym1 === sym2); // true,表示它们是同一个 Symbol

使用Symbol.keyFor(sym)来获取和给定Symbol关联的键。这个方法接受一个 Symbol 作为参数,并返回它在全局符号注册表中的键名。如果 Symbol 不是在全局注册表中创建的,则返回 undefined

const sym = Symbol.for('anotherKey');
console.log(Symbol.keyFor(sym)); // 'anotherKey'

// 对于不在全局注册表中的 Symbol
const localSym = Symbol('localKey');
console.log(Symbol.keyFor(localSym)); // undefined
4.定制内置行为

JavaScript中有些预定义好的Symbol值被称为well-knownsymbol。它们可以指定语言的行为,或者自定义对象的行为以及扩展标准库的行为而不破坏原有的API设计。

const iterableObj = {
  [Symbol.iterator]() {
    let index = 0;
    const data = ['a', 'b', 'c'];
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const item of iterableObj) {
  console.log(item); // 分别输出 'a', 'b', 'c'

总结

Symbol 类型为 JavaScript 提供了一种创建唯一标识符的方法,增强了对象属性的灵活性和安全性。在大型项目中,它是开发人员必不可少的搭档,它不仅能避免命名冲突,还提供了更好的封装性和安全性。正确理解和利用 Symbol 的这些特性,可以帮助开发者编写更加健壮、可维护的代码。