Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
扩展JavaScript Map实现基于内容的对象键管理及生成器方法优化_创想鸟

扩展JavaScript Map实现基于内容的对象键管理及生成器方法优化

扩展JavaScript Map实现基于内容的对象键管理及生成器方法优化

本文深入探讨了如何扩展JavaScript Map类,以支持基于对象内容而非引用的键管理。通过重写set和get方法,实现键的序列化和反序列化,并重点优化了keys()等生成器方法的实现,确保在转换键的同时保留其懒加载特性,避免不必要的性能开销。

JavaScript Map的默认行为:基于引用的对象键

在javascript中,map对象提供了一种存储键值对的强大机制。然而,当使用对象作为键时,map默认采用的是引用相等性(reference equality)来判断键是否相同。这意味着,即使两个对象拥有完全相同的属性和值,只要它们在内存中是不同的实例,map就会将它们视为不同的键。

考虑以下示例:

let newMap = new Map();const obj1 = {'a': 1, 'b' :2};newMap.set(obj1, 123);const copyObj1 = {...obj1}; // 创建一个与obj1内容相同但引用不同的新对象console.log(newMap.get(obj1));      // 输出: 123 (因为是同一个引用)console.log(newMap.get(copyObj1));  // 输出: undefined (因为引用不同)

在这个例子中,尽管 obj1 和 copyObj1 的内容完全一致,newMap.get(copyObj1) 却返回 undefined,因为 Map 比较的是它们的内存地址,而非其内部结构。在某些应用场景中,我们可能需要基于对象内容来识别键,这就需要对Map的行为进行扩展。

实现基于内容的对象键

要实现基于内容的对象键,核心策略是将对象键序列化为字符串。当设置或获取键时,我们首先将对象转换为一个唯一的字符串表示,然后使用这个字符串作为Map的实际键。最常用的序列化方法是 JSON.stringify()。

扩展Map的set和get方法

我们可以通过继承Map类并重写其set和get方法来实现这一功能。以下是一个示例:

立即学习“Java免费学习笔记(深入)”;

interface ISquareCoordinate {    x: number;    y: number;}class ContentBasedMap extends Map {    /**     * 设置键值对。键K会被序列化为字符串。     * @param k 原始对象键     * @param v 对应的值     * @returns 当前Map实例     */    set(k: K, v: V): this {        const stringifiedKey = JSON.stringify(k);        return super.set(stringifiedKey, v);    }    /**     * 根据对象键获取对应的值。键K会被序列化为字符串。     * @param k 原始对象键     * @returns 对应的值,如果不存在则为undefined     */    get(k: K): V | undefined {        const stringifiedKey = JSON.stringify(k);        return super.get(stringifiedKey);    }    /**     * 检查Map中是否存在指定键。键K会被序列化为字符串。     * @param k 原始对象键     * @returns 如果键存在则为true,否则为false     */    has(k: K): boolean {        const stringifiedKey = JSON.stringify(k);        return super.has(stringifiedKey);    }    /**     * 删除Map中指定的键。键K会被序列化为字符串。     * @param k 原始对象键     * @returns 如果成功删除则为true,否则为false     */    delete(k: K): boolean {        const stringifiedKey = JSON.stringify(k);        return super.delete(stringifiedKey);    }}

在这个ContentBasedMap类中,set和get方法在内部将传入的对象键 k 通过 JSON.stringify(k) 转换为字符串,然后调用父类 Map 的相应方法。这样,Map内部实际存储的是字符串键,从而实现了基于内容的对象比较。

优化生成器方法:keys(), values(), entries()

Map类提供了 keys()、values() 和 entries() 等生成器方法,它们返回迭代器,允许我们按需遍历键、值或键值对,而无需一次性加载所有数据到内存中,这对于处理大量数据非常高效。当我们扩展Map并使用字符串化键时,这些生成器方法返回的键也是字符串。我们需要将它们反序列化回原始对象形式,同时保留生成器的懒加载特性。

生成器方法的挑战

一个常见的错误做法是在重写生成器方法时,将父类的迭代器完全展开成一个数组,然后再进行处理。例如:

// 错误示例:丧失懒加载特性* keys(): Generator {   const keysArr = [...super.keys()]; // 这会一次性加载所有键到内存   for (const key of keysArr){      yield JSON.parse(key);   }}

这种方法虽然能达到反序列化的目的,但它会一次性迭代并收集所有父类的键到 keysArr 数组中,从而丧失了生成器方法的懒加载(lazy evaluation)优势。如果Map中包含大量数据,这可能导致显著的内存消耗和性能问题。

正确实现keys()生成器

为了在反序列化键的同时保留懒加载特性,我们应该直接迭代父类的生成器,并在每次迭代时对键进行反序列化并 yield 出去:

class ContentBasedMap extends Map {    // ... (set, get, has, delete 方法与上文相同)    /**     * 返回一个迭代器,按插入顺序遍历Map中的所有原始对象键。     * 保持生成器的懒加载特性。     * @returns 原始对象键的迭代器     */    * keys(): Generator {        const parentKeyIterator = super.keys(); // 获取父类的键迭代器        for (const stringifiedKey of parentKeyIterator) {            yield JSON.parse(stringifiedKey) as K; // 逐个反序列化并yield        }    }}

通过这种方式,ContentBasedMap的keys()方法每次被调用 next() 时,它会调用 super.keys() 的 next() 方法一次,获取一个字符串化的键,然后立即对其进行 JSON.parse 反序列化,并将结果 yield 出去。这样,数据流是按需进行的,完全保留了生成器的懒加载优势。

values()和entries()的类似处理

同样的原则也适用于 values() 和 entries() 方法。values() 返回的是原始值,通常不需要反序列化(除非值本身也是需要特殊处理的序列化对象)。entries() 返回键值对,其中键需要反序列化。

class ContentBasedMap extends Map {    // ... (set, get, has, delete, keys 方法与上文相同)    /**     * 返回一个迭代器,按插入顺序遍历Map中的所有原始值。     * @returns 值的迭代器     */    * values(): Generator {        // 如果值不需要特殊处理,直接迭代父类values即可        yield* super.values();    }    /**     * 返回一个迭代器,按插入顺序遍历Map中的所有原始键值对。     * 键会被反序列化回原始对象形式。     * @returns 原始键值对的迭代器     */    * entries(): Generator {        const parentEntryIterator = super.entries();        for (const [stringifiedKey, value] of parentEntryIterator) {            yield [JSON.parse(stringifiedKey) as K, value];        }    }    // 实现Symbol.iterator以使Map实例可直接用for...of遍历    [Symbol.iterator](): Generator {        return this.entries();    }}

完整示例:ContentBasedMap类

将所有优化后的方法整合,我们可以得到一个功能完整的 ContentBasedMap 类:

interface ISquareCoordinate {    x: number;    y: number;}class ContentBasedMap extends Map {    set(k: K, v: V): this {        const stringifiedKey = JSON.stringify(k);        return super.set(stringifiedKey, v);    }    get(k: K): V | undefined {        const stringifiedKey = JSON.stringify(k);        return super.get(stringifiedKey);    }    has(k: K): boolean {        const stringifiedKey = JSON.stringify(k);        return super.has(stringifiedKey);    }    delete(k: K): boolean {        const stringifiedKey = JSON.stringify(k);        return super.delete(stringifiedKey);    }    * keys(): Generator {        const parentKeyIterator = super.keys();        for (const stringifiedKey of parentKeyIterator) {            yield JSON.parse(stringifiedKey) as K;        }    }    * values(): Generator {        yield* super.values(); // 直接委托给父类迭代器    }    * entries(): Generator {        const parentEntryIterator = super.entries();        for (const [stringifiedKey, value] of parentEntryIterator) {            yield [JSON.parse(stringifiedKey) as K, value];        }    }    // 使ContentBasedMap实例可直接用于for...of循环    [Symbol.iterator](): Generator {        return this.entries();    }}// 使用示例const myContentMap = new ContentBasedMap();const coord1 = { x: 10, y: 20 };const coord2 = { x: 30, y: 40 };const coord1_copy = { x: 10, y: 20 };myContentMap.set(coord1, "Location A");myContentMap.set(coord2, "Location B");console.log("Get coord1:", myContentMap.get(coord1));          // 输出: Location Aconsole.log("Get coord1_copy:", myContentMap.get(coord1_copy)); // 输出: Location A (基于内容匹配)console.log("Has coord1_copy:", myContentMap.has(coord1_copy)); // 输出: trueconsole.log("nIterating keys:");for (const key of myContentMap.keys()) {    console.log(key); // 输出原始对象 { x: 10, y: 20 }, { x: 30, y: 40 }}console.log("nIterating entries:");for (const [key, value] of myContentMap) { // 使用Symbol.iterator    console.log(`Key: ${JSON.stringify(key)}, Value: ${value}`);}myContentMap.delete(coord1_copy); // 基于内容删除console.log("After deleting coord1_copy, has coord1:", myContentMap.has(coord1)); // 输出: false

注意事项与最佳实践

在实现基于内容的Map时,有几个重要的考量点:

性能开销:JSON.stringify()JSON.stringify() 操作本身需要时间和计算资源。对于非常大的对象、深度嵌套的对象或在高性能要求的场景下,频繁的序列化和反序列化可能会成为性能瓶颈。如果键对象结构简单且数量不大,这通常不是问题。

键的确定性:JSON.stringify()的局限性JSON.stringify() 默认情况下对对象的属性顺序不敏感,但对于某些复杂的JavaScript对象(例如包含 undefined、函数、Symbol 或循环引用),它可能无法正确序列化,或者生成不确定性的字符串。

属性顺序: 对于普通对象,JSON.stringify通常会按属性定义的顺序或键的字母顺序(在某些实现中)进行序列化。如果你的对象属性顺序不确定,且 JSON.stringify 行为不一致,可能导致逻辑上相同的对象产生不同的字符串。为了确保确定性,可以考虑在序列化前对对象属性进行排序,或者实现自定义的序列化函数。非JSON安全数据类型: JSON.stringify 会忽略 undefined、函数、Symbol 值,并且会把 Date 对象转换为 ISO 格式字符串。如果你的对象键包含这些类型,JSON.stringify 可能无法提供一个准确反映对象内容的唯一字符串。

键的不可变性一旦一个对象被用作 ContentBasedMap 的键,并被序列化存储,它的字符串表示就固定了。如果在之后修改了原始对象(例如 coord1.x = 99),Map 内部存储的字符串键不会随之更新。这意味着使用修改后的对象去 get 或 delete 将无法匹配,除非你再次 set 它。因此,作为键的对象应该被视为不可变的。

替代方案对于更复杂的对象比较逻辑或更高的性能要求,可以考虑:

自定义哈希函数: 如果 JSON.stringify 不适用,可以实现一个自定义的哈希函数,将对象转换为一个唯一的哈希值(字符串或数字),然后用这个哈希值作为Map的键。第三方库: 某些库(如 lodash.isEqual)提供了深度比较对象的功能。结合这些库,可以实现一个 WeakMap 或一个自定义的 Map 包装器,通过比较对象内容来管理键。WeakMap的局限性: WeakMap 只能使用对象作为键,且键是弱引用,但它同样是基于引用比较的。

总结

通过扩展JavaScript Map类并重写其核心方法,我们可以实现基于对象内容而非引用的键管理,这在许多需要结构化数据作为键的场景中非常有用。关键在于使用 JSON.stringify() 对键进行序列化,并在处理 keys()、values() 和 entries() 等生成器方法时,通过直接迭代父类迭代器并按需反序列化,以保留其懒加载特性,从而避免不必要的性能开销和内存消耗。在实施此类扩展时,务必考虑 JSON.stringify() 的局限性、键的不可变性以及潜在的性能影响,并根据具体需求选择最合适的实现方案。

以上就是扩展JavaScript Map实现基于内容的对象键管理及生成器方法优化的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1520063.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
JavaScript大型数组分页与性能优化教程
上一篇 2025年12月20日 12:59:09
Next.js 部署中的 TypeError: 客户端路由失效与解决方案
下一篇 2025年12月20日 12:59:26

相关推荐

  • 在Java中多态是如何通过虚方法实现的

    多态通过动态方法调度实现,JVM利用虚方法表(vtable)在运行时根据对象实际类型确定方法调用。Java中除private、static、final方法和构造器外均为虚方法,子类重写方法后其vtable指向新实现,调用时JVM通过对象类型查找vtable定位具体方法。如Animal a = new…

    2026年9月21日
    000
  • 如何配置VSCode来完美支持Vue.js开发?

    安装Volar、TypeScript Vue Plugin、ESLint和Prettier扩展,禁用Vetur,在settings.json中配置vetur.enabled为false,设置ESLint保存时自动修复并指定Prettier为默认格式化工具,关联.vue文件语言,启用TypeScrip…

    2026年9月21日
    000
  • 谷歌浏览器官方在线访问 最新版Chrome官网登录

    谷歌浏览器官方在线访问入口是https://www.google.cn/chrome/,提供简洁界面、跨设备同步、高效内核、安全防护和丰富扩展生态。 谷歌浏览器官方在线访问入口在哪里?这是不少网友都关注的,接下来由PHP小编为大家带来最新版Chrome官网登录地址,想要获取纯净浏览体验的网友一起随小…

    2026年9月21日
    200
  • Java Collections.singletonList如何创建单元素集合

    Collections.singletonList(T item) 返回只含一个元素的不可变列表,传入指定对象后生成轻量级只读集合,适用于需高效传递单元素场景。该列表禁止修改操作,否则抛出异常,允许 null 元素,内部优化减少内存开销,常用于 API 参数传递或流处理中的临时数据构造。 Java …

    2026年9月21日
    100
  • JavaScript中的模块联邦如何实现微前端的代码共享?

    模块联邦通过运行时动态加载实现微前端代码共享,无需打包公共依赖。使用 ModuleFederationPlugin 配置 name、remotes、exposes 和 shared,使应用可暴露或引入远程模块,支持组件、工具函数及状态管理共享,提升复用性并减少冗余。 模块联邦通过在构建时让不同应用直…

    2026年9月21日
    200
  • 如何在Java中理解Java I/O与NIO机制

    传统I/O是阻塞式流模型,适用于低并发场景;NIO基于缓冲区与通道,支持非阻塞和多路复用,适合高并发网络应用,核心区别在于线程模型与资源利用率。 Java中的I/O(输入/输出)与NIO(New I/O)是处理数据读写的核心机制,理解它们的区别和使用场景对开发高性能应用至关重要。传统I/O基于流模型…

    2026年9月21日
    100
  • UC浏览器网页上的文字无法选中复制怎么办 UC浏览器解决网页文字禁止复制问题

    答案:可通过开发者工具、阅读模式、打印预览、OCR识别或自定义脚本解除UC浏览器网页复制限制。具体操作依次为:开启开发者工具并执行JavaScript代码解除限制;启用阅读模式净化页面内容;使用打印预览重新渲染页面以选中文字;对截图应用OCR技术提取文本;添加书签脚本自动移除禁用选择的代码,从而实现…

    2026年9月21日
    100
  • JavaScript中的尾调用优化(TCO)在ES6中如何工作?

    尾调用是指函数的最后一个动作调用另一个函数,ES6引入尾调用优化以重用栈帧、避免内存溢出,支持真正的尾递归,如阶乘函数通过累积参数实现。 尾调用优化(Tail Call Optimization, TCO)是ES6引入的一项语言特性,目的是在特定条件下重用函数调用栈帧,避免不必要的内存增长,从而支持…

    2026年9月21日
    200
  • Java语法基础有哪些新手必学的核心知识

    掌握Java基本数据类型与变量声明,如int、double、char和boolean,并理解强类型语言特性;2. 熟悉运算符与表达式,包括算术、比较和逻辑运算符,奠定程序逻辑基础。 Java语法基础是每个初学者必须掌握的内容,只有打好根基,才能顺利进阶面向对象编程和实际项目开发。以下是新手必学的核心…

    2026年9月21日
    300
  • 如何在Java中实现个人财务管理工具

    首先设计Transaction、FinanceManager和Budget核心类,实现交易记录、统计分析与预算控制功能,通过ArrayList管理数据,使用LocalDate处理日期,结合ObjectOutputStream持久化存储,初期采用Scanner构建控制台菜单实现增删查改与报表展示,后期…

    2026年9月21日
    100
  • VSCode的代码折叠功能好用吗?

    VSCode代码折叠功能支持多种方式:点击箭头、快捷键、命令面板及按区域类型折叠;可自定义基于缩进的折叠、默认层级和提示装饰器;集成语言服务后能智能识别JSX、Vue组件等结构,提升大型文件编辑效率。 VSCode 的代码折叠功能非常实用,尤其在处理大型文件或复杂结构时能显著提升阅读和编辑效率。 支…

    2026年9月21日
    100
  • 如何备份VSCode的全部设置和扩展?

    备份VSCode全部设置和扩展需保存配置文件与扩展目录;2. 配置文件位于各系统指定路径的User文件夹内,包含settings.json和keybindings.json;3. 通过code –list-extensions导出扩展列表并用xargs批量重装可恢复扩展;4. 推荐直接复…

    2026年9月21日
    100
  • Laravel 8 登录后重定向到仪表盘的全面指南

    本文深入探讨了 Laravel 8 中用户登录后重定向到仪表盘的多种策略。我们将详细解析默认的重定向机制,包括 LoginController 和 RedirectIfAuthenticated 中间件,并重点介绍如何通过自定义登录逻辑实现精确的重定向控制,同时提供示例代码和常见问题排查建议,确保用…

    2026年9月21日
    100
  • Guava Multimap:高效获取并打印指定键的所有关联值

    guava multimap是处理一键多值映射关系的强大工具。要获取特定键的所有关联值,应直接使用其提供的`multimap#get(k)`方法。该方法会返回一个包含所有匹配值的`collection`,即使键不存在,也会返回一个空集合而非`null`,从而简化了值检索和空值处理逻辑,是比手动迭代键…

    2026年9月21日
    100
  • 怎样配置VSCode与Jest、Cypress等测试框架进行集成测试?

    首先安装Jest和Cypress插件及依赖,配置jest.config.js和.vscode/settings.json实现Jest自动运行,再通过launch.json添加Cypress调试配置,最后在package.json中定义统一脚本命令,使两者在VSCode中高效协同工作。 要在 VSCo…

    2026年9月21日
    000
  • Java Stream 高效分组计数并获取Top N元素

    本文深入探讨了如何利用java stream api对数据进行高效的分组计数,并从中提取出现频率最高的top n元素。文章首先介绍了一种简洁的基于全排序的实现方式,该方法适用于数据集较小或top n值接近总数的情况。随后,针对大数据量和小型top n场景下的性能瓶颈,文章详细阐述了如何通过自定义`c…

    2026年9月21日
    100
  • VSCode的括号匹配功能如何自定义?

    可通过 settings.json 自定义括号高亮的边框和背景色;2. 用 editor.matchBrackets 控制是否启用高亮;3. 启用 bracketPairColorization 可为嵌套括号着色;4. 使用 Ctrl/Cmd + Shift + 快速跳转配对括号。 VSCode 的…

    2026年9月21日
    100
  • JSF应用中Markdown文档动态链接处理指南

    本教程旨在解决jsf web应用程序中集成markdown文档时,如何动态处理内部链接以实现页面局部更新的问题。通过结合服务器端markdown渲染和客户端javascript事件监听,我们可以拦截markdown生成的html链接点击事件,利用ajax异步加载并渲染目标markdown文件,从而在…

    2026年9月21日
    600
  • mysql如何理解数据压缩

    MySQL数据压缩通过减少存储空间提升I/O效率,主要在InnoDB引擎中实现页级压缩,使用zlib算法对BLOB、TEXT等大字段表压缩效果显著,需设置ROW_FORMAT=COMPRESSED和KEY_BLOCK_SIZE;压缩可降低磁盘使用并加速全表扫描,但增加CPU开销,频繁更新可能导致页分…

    2026年9月21日
    000
  • 在Java中如何实现线程优先级控制

    Java中线程优先级通过Thread类实现,取值范围1-10,分别对应MIN_PRIORITY、NORM_PRIORITY和MAX_PRIORITY;新线程继承父线程优先级,可通过setPriority()设置;尽管高优先级线程更可能被调度,但执行顺序不保证,因受操作系统影响;应避免依赖优先级控制关…

    2026年9月21日
    100

发表回复

登录后才能评论
关注微信