You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为TypeScript类定义哈希函数以用作字典/Map的键

TypeScript中将类对象作为字典键的解决方案

问题根本原因

JavaScript/TypeScript 内置的Map默认使用*严格相等(===)*规则判断键是否相同:对于引用类型的对象,严格相等判断的是内存地址是否一致,而非属性值是否相等,因此两个属性完全相同的Point实例会被识别为不同的键。

惯用解决方案

下面是生产环境常用的三种实现方式,可根据业务场景选择:

方案1:自定义实例序列化方法 + 封装专用Map

这是适用范围最广的方案,不需要修改实例创建逻辑,性能和可靠性都比较高。
首先给类添加序列化方法,生成可以唯一标识实例属性的字符串:

class Point {
    constructor(public x: number, public y: number) {}

    // 生成唯一标识实例的字符串,JSON序列化可以避免属性值包含分隔符导致的冲突
    get uniqueKey(): string {
        return JSON.stringify({ type: 'Point', x: this.x, y: this.y });
    }
}

然后封装适配Point类型的Map类,对外暴露和原生Map完全一致的使用接口:

class PointMap<V> implements Map<Point, V> {
    private innerMap = new Map<string, V>();

    set(key: Point, value: V): this {
        this.innerMap.set(key.uniqueKey, value);
        return this;
    }

    get(key: Point): V | undefined {
        return this.innerMap.get(key.uniqueKey);
    }

    has(key: Point): boolean {
        return this.innerMap.has(key.uniqueKey);
    }

    delete(key: Point): boolean {
        return this.innerMap.delete(key.uniqueKey);
    }

    clear(): void {
        this.innerMap.clear();
    }

    get size(): number {
        return this.innerMap.size;
    }

    // 剩余Map接口方法可按需实现,这里省略其余遍历相关方法的封装
}

使用效果如下:

const map = new PointMap<number>();
map.set(new Point(0, 1), 1);
map.set(new Point(0, 1), 2);
console.log(map.size); // 输出 1,符合预期
console.log(map.get(new Point(0, 1))); // 输出 2

方案2:享元模式复用实例

如果相同属性的实例重复率很高,可以用享元模式保证同属性永远只有一个实例,直接使用原生Map即可:

class Point {
    // 全局实例缓存
    private static instanceCache = new Map<string, Point>();

    // 构造函数私有化,禁止外部直接new
    private constructor(public x: number, public y: number) {}

    // 统一使用静态工厂方法获取实例
    static create(x: number, y: number): Point {
        const cacheKey = `${x},${y}`;
        if (!Point.instanceCache.has(cacheKey)) {
            Point.instanceCache.set(cacheKey, new Point(x, y));
        }
        return Point.instanceCache.get(cacheKey)!;
    }
}

使用时不要直接new Point,统一调用Point.create生成实例:

const map = new Map<Point, number>();
map.set(Point.create(0, 1), 1);
map.set(Point.create(0, 1), 2);
console.log(map.size); // 输出 1,符合预期

该方案的优势是无需修改Map的使用逻辑,缺点是会长期缓存所有创建过的实例,适合实例数量可控、重复率高的场景。

方案3:直接使用序列化字符串作为键

如果只是临时使用,不想额外封装类,可以直接把序列化后的字符串作为原生Map的键,是最轻便的实现:

const map = new Map<string, number>();
const p1 = new Point(0, 1);
// 存入时取实例的序列化值作为键
map.set(JSON.stringify({x: p1.x, y: p1.y}), 1);

const p2 = new Point(0, 1);
// 取值时同样用当前实例的序列化值查找
console.log(map.get(JSON.stringify({x: p2.x, y: p2.y}))); // 输出 1

注意事项

序列化时要避免不同属性生成相同的字符串:如果类属性包含字符串、对象等复杂类型,优先使用JSON.stringify序列化完整的属性结构,不要简单用逗号拼接属性值,避免属性值包含分隔符导致的键冲突。

内容的提问来源于stack exchange,提问作者Yuchen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 15:45:00