[TypeScript学习笔记-10]一个特殊且重要的类型:Symbol

在 TypeScript 中,Symbol 是 ES6 引入的一种原始数据类型,与 numberstringboolean 等基础类型并列。它表示一个独一无二、不可变的值,通常被用作对象属性的键,以确保属性名不会发生冲突。

在 TypeScript 中声明一个 Symbol 类型的变量,写法如下:

typescript 复制代码
let sym: symbol = Symbol("描述信息");

这里的 "描述信息" 只是一个可选的描述字符串,仅用于调试或日志输出,并不会影响 Symbol 本身的唯一性。即使两个 Symbol 使用相同的描述,它们也是完全不同的两个值。

1. Symbol 具有怎样的特性(举例)

Symbol 的核心特性可以概括为以下几点:

1.1 唯一性

每次调用 Symbol() 都会生成一个全新的、独一无二的值,即使描述相同也不相等:

typescript 复制代码
const s1 = Symbol("key");
const s2 = Symbol("key");

console.log(s1 === s2); // false

1.2 不可变性

Symbol 一旦创建,其值就无法被修改,它本身是一个原始值,不是对象,因此也不能调用 new Symbol()

typescript 复制代码
// 错误写法:Symbol 是原始类型,不能使用 new
// const s = new Symbol();

// 正确写法
const s = Symbol("ok");

1.3 可作为对象属性的键

Symbol 最常见的用途是作为对象属性的键,从而避免属性名冲突:

typescript 复制代码
const uniqueKey = Symbol("id");

const obj = {
  [uniqueKey]: 123,
  name: "Tom"
};

console.log(obj[uniqueKey]); // 123

1.4 不会被常规遍历方法枚举

使用 for...inObject.keys() 遍历对象时,Symbol 类型的属性键不会被枚举出来:

typescript 复制代码
const key = Symbol("hidden");
const obj = { name: "Tom", [key]: "secret" };

console.log(Object.keys(obj)); // ["name"]
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(hidden) ]

2. unique Symbol 如何正确理解

在 TypeScript 中,如果仅仅把变量推导为 symbol 类型,类型系统是无法分清具体的 Symbol 的。比如在如下的例子中,在 TypeScript 看来,foobar 都是 Symbol 类型,它可以让你写 bar = foo,但这在逻辑上可能是不合理的。

typescript 复制代码
let foo: symbol = Symbol("Foo");
let bar : symbol = Symbol("bar");
bar= foo;

为了解决这个问题,TypeScript 引入了 unique symbol。它是 symbol 的子类型(Subtype),代表某一个特定具体独一无二 的 Symbol 类型。严格的使用限制因为 unique symbol 代表一个具体的、不可变的值,所以 TypeScript 对它有极其严格的语法要求:必须使用 const 声明(不能用 let 或 var),或者作为类(Class)的 readonly static 属性。不能手动显式指定类型为 unique symbol 却赋值一个普通变量,它只能由 TypeScript 通过 const 自动推导出来。

typescript 复制代码
const foo: unique symbol = Symbol("Foo") ; // OK
/// (TS) A variable whose type is a 'unique symbol' type must be 'const'.
let bar: unique symbol = Symbol("Bar") ; 
// (TS) A variable whose type is a 'unique symbol' type must be 'const'.
var baz : unique symbol = Symbol("Baz") ; 

class cls {
    // OK
    static readonly foo: unique symbol = Symbol("Bar") ; 

    // (TS) A property of a class whose type is a 'unique symbol' type must be both 'static' and 'readonly'.
    static bar: unique symbol = Symbol("Bar") ; 
}

3. Symbol 的具体应用场景

Symbol 是 TypeScript 中一个非常特殊的原始类型,它的核心价值在于唯一性 。无论是作为对象属性的键来避免冲突,还是借助 unique symbol 在类型层面区分不同的标记,Symbol 都为开发者提供了一种更安全、更灵活的设计手段。在实际项目中,合理使用 Symbol 可以让代码结构更清晰,也能减少因命名冲突而引发的潜在问题。

3.1 防止对象属性名冲突

当多个模块或库需要往同一个对象上挂载属性时,使用 Symbol 作为键可以避免命名冲突:

typescript 复制代码
const libA = Symbol("libA");
const libB = Symbol("libB");

const sharedObj: any = {};
sharedObj[libA] = "A 库的数据";
sharedObj[libB] = "B 库的数据";

console.log(sharedObj[libA]); // A 库的数据
console.log(sharedObj[libB]); // B 库的数据

3.2 定义类的"隐藏"成员

利用 Symbol 属性不会被常规方法枚举的特性,可以在类中定义一些内部使用的成员:

typescript 复制代码
const _count = Symbol("count");

class Counter {
  private [_count]: number = 0;

  increment() {
    this[_count]++;
  }

  get value() {
    return this[_count];
  }
}

const c = new Counter();
c.increment();
c.increment();
console.log(c.value); // 2

3.3 实现全局注册表(Symbol.for)

Symbol.for() 会在全局注册表中查找并复用已存在的 Symbol,适合在多个模块间共享同一个 Symbol:

typescript 复制代码
const s1 = Symbol.for("shared");
const s2 = Symbol.for("shared");

console.log(s1 === s2); // true

4. 使用内置的 Symbol 常量

JavaScript 提供了一些内置的 Symbol 常量,用于自定义对象的基础行为。

4.1 Symbol.iterator & Symbol.asyncIterator

Symbol.iteratorSymbol.asyncIterator分别用于定义一个同步和异步迭代器, 前者会在对象被用于 for...of 循环、解构赋值(... 展开运算符)或 Array.from() 时被调用, 后者则在for await...of 循环遍历异步数据源被调用。

typescript 复制代码
const range = {
  from: 1, to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    return {
      next: () => current <= this.to ? { value: current++, done: false } : { done: true }
    };
  }
};
for (let num of range) console.log(num); // 输出: 1, 2, 3

4.2 Symbol.toPrimitive

Symbol.toPrimitive用于定义当前对象针对指定基元类型(数字、字符串、布尔值)的转换规则。自定义对象在被转换为原始类型值时的行为。对象参与数学运算(如 +、-)或字符串拼接、比较时会被调用。在下面的演示程序中,我们定义了常量对象PI,并利用将此Symbol作为属性名称定义了类型转换函数。

typescript 复制代码
const PI = {
  [Symbol.toPrimitive](hint:string) {
    if (hint === 'number') return 3.14;
    if (hint === 'string') return 'π';
    return true; // 默认
  }
};


console.log(`${PI}`); //'π'
console.log(PI as any * Math.pow(2,2)); // 12.56

4.3 Symbol.hasInstance

Symbol.hasInstance用来自定义 instanceof 运算符的识别逻辑。当执行 obj instanceof MyClass 时,实际上是在调用 MyClass[Symbol.hasInstance](obj)。在下面的演示程序中,我们定义了表示质数的类型PrimeNumber,并以此Symbol命名了一个静态方法用来确定指定的对象是否是一个质数。

typescript 复制代码
class PrimeNumber
{    
    static [Symbol.hasInstance](num:any)
    {
        return PrimeNumber.isPrimeNumber(num)
    }

    static isPrimeNumber(num:any)
    {
        if (num <= 1 || !Number.isInteger(num)) return false;
        if (num === 2) return true;
        if (num % 2 === 0) return false;
        const boundary = Math.sqrt(num);
        for (let i = 3; i <= boundary; i += 2) 
        {
            if (num % i === 0) return false;
        }
        return true; 
    }
}

console.log(2 as any instanceof PrimeNumber) // True
console.log(3 as any instanceof PrimeNumber) // True
console.log(4 as any instanceof PrimeNumber) // False

4.4 其它

TypeScript还为很多常用的应用场景定义了对应的Symbol, 比如当你在字符串上调用 matchreplacesearchsplit 等方法,且传入的参数是一个自定义对象时,你可以通过以下符号拦截这些操作:

  • Symbol.match :改变 String.prototype.match() 的行为。也用于 isRegExp 的内部判断;
  • Symbol.replace :改变 String.prototype.replace() 的行为,允许自定义替换逻辑;
  • Symbol.search :改变 String.prototype.search() 的行为;
  • Symbol.split :改变 String.prototype.split() 的行为。
相关推荐
To_OC12 小时前
啃完 TS 工具类型我发现:Pick 和 Omit 原来就是一层窗户纸
前端·面试·typescript
JaydenAI15 小时前
[TypeScript学习笔记-07]根据现有类型创建新类型的N种方式
typescript
东风破_20 小时前
TypeScript 高级类型进阶:keyof、Exclude、Record 与类型组合思想
前端·后端·typescript
晓说前端1 天前
TypeScript 核心语法应用 —— Vue 3 中的使用(下)
前端·javascript·typescript·类型系统
东风破_1 天前
(拼多多考题)TypeScript 工具类型:Pick、Omit、Partial、Required
typescript
rimydu1974art1 天前
opencheck解读:拆解 OpenCheck 的歧视性条款识别与澄清问询机制
人工智能·typescript
触底反弹2 天前
🔥 从「为什么」到「怎么用」:TypeScript 类型约束与泛型完全指南
后端·面试·typescript
Asize2 天前
2 道大厂面试题:TS 工具类型我懂了,CSS 3 列布局把我问住了
前端·css·typescript
电脑玩家柒柒2 天前
DeepSeek Harness 生态观察:插件一夜爆火、标准预设与桌面 Studio,Agent 框架的安卓时刻来了吗
typescript