
javascript的map对象在处理键时,默认采用严格相等(===)进行比较。这意味着当使用对象作为键时,map会基于对象的内存引用进行比较,而非其内部属性或值。这在某些场景下可能会导致非预期的行为。
例如,以下代码演示了Map的默认行为:
let newMap = new Map();
const obj1 = {'a': 1, 'b' :2};
newMap.set(obj1, 123);
const copyObj1 = {...obj1}; // 创建了一个内容相同但引用不同的新对象
console.log(newMap.get(obj1)); // 输出: 123 (因为引用相同)
console.log(newMap.get(copyObj1)); // 输出: undefined (因为引用不同)为了实现newMap.get(copyObj1)也能返回123,我们需要一种机制,让Map能够基于对象的内容进行键的比较和查找。
要解决Map基于引用比较对象键的问题,一个常见的策略是在内部将对象键序列化为字符串。这样,即使是内容相同的不同引用对象,只要其序列化后的字符串相同,就能被视为同一个键。我们可以通过扩展Map类来实现这一点。
interface ISquareCoordinate {
x: number;
y: number;
}
class CoordMapper extends Map<string, Array<ISquareCoordinate>> {
/**
* 重写set方法,将对象键序列化为字符串
* @param k 作为键的对象
* @param v 对应的值
* @returns 当前Map实例
*/
set(k: ISquareCoordinate, v: Array<ISquareCoordinate>): this {
const stringifiedKey = JSON.stringify(k);
return super.set(stringifiedKey, v);
}
/**
* 重写get方法,将对象键序列化后进行查找
* @param k 作为键的对象
* @returns 对应的值,如果不存在则返回undefined
*/
get(k: ISquareCoordinate): Array<ISquareCoordinate> | undefined {
const stringifiedKey = JSON.stringify(k);
return super.get(stringifiedKey);
}
/**
* 重写has方法,支持基于对象内容的键检查
* @param k 作为键的对象
* @returns 如果Map中存在该键,则返回true,否则返回false
*/
has(k: ISquareCoordinate): boolean {
const stringifiedKey = JSON.stringify(k);
return super.has(stringifiedKey);
}
/**
* 重写delete方法,支持基于对象内容的键删除
* @param k 作为键的对象
* @returns 如果成功删除,则返回true,否则返回false
*/
delete(k: ISquareCoordinate): boolean {
const stringifiedKey = JSON.stringify(k);
return super.delete(stringifiedKey);
}
}在上述CoordMapper类中,我们重写了set、get、has和delete方法。在这些方法内部,传入的对象键k首先通过JSON.stringify(k)转换为字符串,然后将这个字符串作为实际的键传递给父类Map的对应方法。这样,无论传入的是原始对象还是其内容相同的副本,只要JSON.stringify的结果一致,Map就能正确地处理它们。
立即学习“Java免费学习笔记(深入)”;
Map类提供了keys()、values()和entries()等生成器方法,它们返回迭代器,允许我们惰性地遍历集合中的元素。然而,在扩展类中重写这些方法时,需要注意保持其惰性特性,避免一次性加载所有数据到内存中。
考虑一个不优化的keys()实现:
// 不推荐的实现方式:一次性加载所有键
* keys(): Generator<ISquareCoordinate> {
const keysArr = [...super.keys()]; // 将所有键加载到数组中
for (const key of keysArr){
yield JSON.parse(key); // 然后逐个解析并yield
}
}这种实现方式的问题在于[...super.keys()]会立即迭代父类Map的所有键并将其收集到一个数组中。如果Map中包含大量键,这会导致显著的内存开销和性能下降,失去了生成器应有的惰性优势。
正确的优化方法是直接迭代父类生成器返回的迭代器,并逐个处理:
class CoordMapper extends Map<string, Array<ISquareCoordinate>> {
// ... (set, get, has, delete 方法如上)
/**
* 优化后的keys()生成器方法,保持惰性迭代
* @returns 一个生成器,每次yield一个解析后的对象键
*/
* keys(): Generator<ISquareCoordinate, void, unknown> {
const parentKeyIterator = super.keys(); // 获取父类的键迭代器
for (const key of parentKeyIterator) {
yield JSON.parse(key); // 逐个解析并yield
}
}
/**
* 优化后的values()生成器方法 (示例,实际根据需求实现)
* @returns 一个生成器,每次yield一个值
*/
* values(): Generator<Array<ISquareCoordinate>, void, unknown> {
const parentValueIterator = super.values();
for (const value of parentValueIterator) {
yield value; // 值通常不需要额外处理
}
}
/**
* 优化后的entries()生成器方法 (示例,实际根据需求实现)
* @returns 一个生成器,每次yield一个 [解析后的键, 值] 对
*/
* entries(): Generator<[ISquareCoordinate, Array<ISquareCoordinate>], void, unknown> {
const parentEntryIterator = super.entries();
for (const [stringifiedKey, value] of parentEntryIterator) {
yield [JSON.parse(stringifiedKey), value];
}
}
}通过直接迭代super.keys()返回的parentKeyIterator,我们确保了keys()生成器在每次调用next()时,才会向父类迭代器请求下一个键,然后进行JSON.parse并yield。这种方式完美地保留了生成器的惰性特性,避免了不必要的内存消耗。values()和entries()方法也可以采用类似的模式进行优化。
JSON.stringify的局限性:
性能开销: 频繁地进行JSON.stringify和JSON.parse操作会带来一定的性能开销。对于键操作非常频繁且性能要求极高的场景,需要权衡这种便利性与性能损耗。
类型安全: 在TypeScript环境中,为扩展类及其方法定义准确的类型签名至关重要,如示例中所示,确保代码的健壮性和可维护性。
键的不可变性: 理想情况下,作为键的对象应该是不可变的。如果键对象在存储后被修改,其序列化后的字符串可能会发生变化,导致后续的get或has操作失败,因为它们会尝试查找基于新序列化字符串的键。
通过扩展Map类并重写其核心方法,我们可以有效地实现基于对象内容而非引用的键存储机制,极大地增强了Map在特定场景下的灵活性。同时,在重写keys()、values()和entries()等生成器方法时,务必注意保持其惰性迭代的特性,避免不必要的性能和内存开销。理解JSON.stringify的优缺点及其对键对象的要求,有助于构建更健壮和高效的数据结构。
以上就是JavaScript Map扩展:实现对象键值化与生成器惰性迭代优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号