Policy Maps and Sets S1
中文标题:JavaScript 的策略 Map 和 Set
- 阶段: Stage 1
- 状态: 进行中
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案旨在为 JavaScript 语言添加内置的、支持缓存替换策略(如 LRU、LFU、FIFO 和 LIFO)的类似于 Map 和 Set 的数据结构。开发者常将映射数据结构用作缓存,并需要限制其内存消耗,但重新实现替换策略是重复性的工作。提案将提供如 FIFOMap、LIFOMap、LRUMap、LFUMap 以及对应的 Set 版本等构造函数,并支持可配置的最大容量。此外,它还考虑了一种替代设计,即向现有 Map 和 Set 构造函数添加可选参数。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
JavaScript 的策略 Map 和 Set
ECMAScript 第 1 阶段提案。2022 年。
提案负责人:Hemanth HM;J. S. Choi;Shu-yu Guo。
理由
开发者经常使用映射数据结构作为缓存,并且他们常常希望限制这些缓存的内存消耗。缓存替换策略 很常见、有用、重复,而且重新实现起来很烦人。
例如,提案中的记忆函数(用于函数记忆化)将使用类似 Map 的对象来存储最近函数调用的参数和结果,让开发者能够自行决定所需的缓存替换策略和限制:
另一个用例可能是大型应用的内存管理,作为使用 WeakRefs 或暴露垃圾回收钩子的一种替代方案。(参见议题 #4。)
然而,如果没有内置的带缓存策略的 Map 类,要为用户态库或手写数据结构中的记忆函数分配策略,将是困难、烦人且不符合人体工程学的。
因此,我们提议探索在 JavaScript 语言中加入支持各种基本、简单缓存替换策略的映射数据结构,例如 LRU(最近最少使用)、LFU(最不经常使用)、FIFO(先进先出)和 LIFO(后进先出)。
如果此提案获批进入第 1 阶段,那么我们将探索这些数据结构设计的多个方向,并决定添加哪些最合适的策略。我们还将尽可能收集真实世界的用例,并据此调整设计以满足这些用例。
此外,如果记忆函数提案和本提案都获批进入第 1 阶段,那么我们将探索记忆化函数如何利用这些数据结构来控制其缓存的内存使用。
可能的解决方案
该提案将向全局对象添加多个内置类。这些类中的每一个都具有可变的、类似 Map 的接口或类似 Set 的接口(尽管它们都不是 Map 或 Set 的实际子类;参见议题 #1)。
对于类似 Map 的类,这些方法包括:
m.size:m 中条目的数量。
m.has(key):返回一个布尔值,表示 m 是否具有给定 key 的条目。
m.get(key):如果存在给定 key 对应的条目,则返回 m 中该条目的值;否则返回 undefined。
m.set(key, value):向 m 添加一个条目,将给定 key 映射到给定 value。返回 m 本身。
m.delete(key):删除 m 中具有给定 key 的条目(如果有)。返回一个布尔值,表示 m 在删除之前是否确实具有该 key 的条目。
m.clear():从 m 中移除所有条目。返回 undefined。
m[Symbol.iterator]():不确定我们是否应该实现。参见议题 #3。
m.entries():不确定我们是否应该实现。参见议题 #3。
m.keys():不确定我们是否应该实现。参见议题 #3。
m.values():不确定我们是否应该实现。参见议题 #3。
m.forEach():不确定我们是否应该实现。参见议题 #3。
对于类似 Set 的类,这些方法包括:
s.size:s 中值的数量。
s.has(value):返回一个布尔值,表示 s 是否具有给定 value。
s.add(value):将给定 value 添加到 s。返回 s 本身。
s.delete(key):如果 s 具有 value,则从 s 中删除给定 value。返回一个布尔值,表示在删除 value 之前 s 是否确实具有 value。
s.clear():从 s 中移除所有值。返回 undefined。
m[Symbol.iterator]():不确定我们是否应该实现。参见议题 #3。
s.values():不确定我们是否应该实现。参见议题 #3。
s.forEach():不确定我们是否应该实现。参见议题 #3。
FIFOMap 和 FIFOSet
如果向这些构造函数传入非整数的条目/值最大数量,或者初始条目/值不可迭代,它们会抛出 TypeError。
它们的实例会按照添加顺序淘汰条目/值,就像它们是先进先出队列一样。
LIFOMap 和 LIFOSet
这些构造函数如果被赋予非整数的条目/值最大数量,或者初始条目/值不可迭代,则会抛出 TypeError。
它们的实例会按照添加顺序淘汰条目/值,就像它们是后进先出栈一样。
LRUMap 和 LRUSet
这些构造函数如果被赋予非整数的条目/值最大数量,或者初始条目/值不可迭代,则会抛出 TypeError。
它们的实例首先淘汰最近最少通过 .get(对于 LRUMap)或 .has(对于 LRUSet)访问的条目/值。
LFUMap 和 LFUSet
这些构造函数如果被赋予非整数的条目/值最大数量,或者初始条目/值不可迭代,则会抛出 TypeError。
它们的实例首先淘汰最不经常通过 .get(对于 LRUMap)或 .has(对于 LRUSet)访问的条目/值。
替代解决方案
或者,我们也可以向现有的 Map 和 Set 构造函数添加可选参数:
真实世界示例
先例与 Web 兼容性
- collections.js
- Google Guava 缓存
- Python functools.lru_cache
- rsms/js-lru API 包括迭代器(iterators)、newest 和 oldest。