面试题:️ES6 新增的 Symbol 基础数据类型有什么作用?

ES6 引入的 Symbol 是 JavaScript 的第七种原始数据类型(primitive data type),其他六种是:stringnumberbooleannullundefinedobjectobject 是引用类型,但 typeof null 也返回 "object")。

Symbol 的核心特性是:创建唯一且不重复的值。即使你用相同的描述创建两个 Symbol,它们也是完全不同的。

1. Symbol 的基本用法

// 创建一个 Symbol
const sym1 = Symbol('description');
const sym2 = Symbol('description');

// 它们是唯一的,即使描述相同
console.log(sym1 === sym2); // false

// typeof 检查
console.log(typeof sym1); // "symbol"

2. Symbol 的主要作用和应用场景

(1) 作为对象的唯一属性名(防止属性名冲突)

这是 Symbol 最经典和最重要的用途。可以用来创建一个不会与其他任何属性名冲突的“私有”或“隐藏”属性。

// 场景:为一个库添加一个方法,避免与用户已有的属性冲突
const MY_LIBRARY_KEY = Symbol('myLibraryInternal');

const userObj = {
  name: 'Alice',
  [MY_LIBRARY_KEY]: 'Internal data' // 使用 Symbol 作为属性名
};

// 这个属性不会被普通的 for...in 或 Object.keys() 枚举到
console.log(Object.keys(userObj)); // ['name']
console.log(Object.getOwnPropertyNames(userObj)); // ['name']

// 但可以通过 Object.getOwnPropertySymbols() 获取
console.log(Object.getOwnPropertySymbols(userObj)); // [Symbol(myLibraryInternal)]

// 访问属性
console.log(userObj[MY_LIBRARY_KEY]); // 'Internal data'

注意:这里的“私有”是相对的,因为 Object.getOwnPropertySymbols() 仍然可以获取到。真正的私有属性在 ES2019+ 中通过 # 前缀实现。

(2) 定义常量,避免字符串常量的值冲突

// 使用字符串常量,可能意外相等
const COLOR_RED = 'red';
const USER_STATUS_RED = 'red';
console.log(COLOR_RED === USER_STATUS_RED); // true (逻辑上不应相等!)

// 使用 Symbol,保证唯一性
const COLOR_RED = Symbol('red');
const USER_STATUS_RED = Symbol('red');
console.log(COLOR_RED === USER_STATUS_RED); // false (完美区分)

(3) 作为“元属性”或“标记”(Metadata)

可以给对象添加一些元信息,而不会干扰对象的正常属性。

const obj = {};

// 用 Symbol 标记这个对象是一个“可序列化”的
obj[Symbol.for('serializable')] = true;

// 其他代码可以检查这个标记
if (obj[Symbol.for('serializable')]) {
  // 执行序列化逻辑
}

(4) 使用全局 Symbol 注册表 (Symbol.for()Symbol.keyFor())

Symbol.for(key) 会在一个全局注册表中查找或创建一个 Symbol。如果 key 相同,则返回同一个 Symbol 实例。这与直接调用 Symbol() 不同。

// 创建全局 Symbol
const symA = Symbol.for('sharedKey');
const symB = Symbol.for('sharedKey');

// 它们是相等的!
console.log(symA === symB); // true

// 获取 Symbol 的键
console.log(Symbol.keyFor(symA)); // 'sharedKey'

用途:在不同模块或代码库之间共享一个唯一的 Symbol,而不需要直接传递该 Symbol 变量。

(5) 拦截对象操作(Symbol 作为“知名 Symbol”)

ES6 定义了一些以 Symbol. 开头的知名 Symbol(Well-Known Symbols),它们代表了 JavaScript 语言内部的行为。你可以用这些 Symbol 来自定义对象的行为。

  • Symbol.iterator:定义对象的默认迭代器,使其可被 for...of 循环。
    const myIterable = {
      [Symbol.iterator]() {
        let step = 0;
        return {
          next() {
            step++;
            if (step <= 3) {
              return { value: step, done: false };
            }
            return { done: true };
          }
        };
      }
    };
    
    for (const value of myIterable) {
      console.log(value); // 1, 2, 3
    }
  • Symbol.toStringTag:自定义 Object.prototype.toString() 的返回值。
    const myObj = {
      [Symbol.toStringTag]: 'MyCustomType'
    };
    console.log(Object.prototype.toString.call(myObj)); // "[object MyCustomType]"
  • Symbol.hasInstance:自定义 instanceof 操作符的行为。
  • Symbol.toPrimitive:自定义对象转换为原始值的行为。

3. Symbol 的重要特性

  1. 唯一性:每个通过 Symbol() 创建的值都是唯一的。
  2. 不可变性Symbol 值本身是不可改变的(immutable)。
  3. 非字符串性:不能被隐式转换为字符串,否则会抛出错误。
    const sym = Symbol('test');
    // console.log('prefix' + sym); // TypeError!
    console.log('prefix' + sym.toString()); // "prefixSymbol(test)"
    console.log(`prefix${sym}`); // 同样会报错!需要 sym.toString()
  4. 不被枚举:使用 Symbol 作为属性名时,该属性不会被 for...infor...ofObject.keys()Object.getOwnPropertyNames() 枚举到。只能通过 Object.getOwnPropertySymbols()Reflect.ownKeys() 获取。
  5. JSON 安全JSON.stringify() 会忽略以 Symbol 为键的属性。

4. 总结

Symbol 数据类型的主要作用是:

  1. 创建唯一标识符:确保值的唯一性,避免命名冲突。
  2. 作为私有/隐藏属性名:为对象添加元信息或内部属性,不干扰正常属性枚举。
  3. 定义常量:创建不会意外相等的常量。
  4. 实现元编程:通过知名 Symbol(如 Symbol.iterator)自定义对象的底层行为。

简单来说,Symbol 就像一个“带名字的唯一标签”,它既保证了唯一性,又能作为属性名隐藏在对象的“幕后”,是构建健壮、无冲突的库和框架的有力工具。

THE END
喜欢就支持一下吧
点赞6 分享