Map.size 可 O(1) 获取键值对数量,而 Object.keys(obj).length 需遍历生成数组,本质不同;Map 支持任意类型 key、保持插入顺序、精确反映自身可枚举属性数,适用于需频繁增删查数的场景。

Map.size 能直接返回键值对数量,不需要遍历
Map 实例的 size 是一个只读属性,底层由引擎维护,每次增删操作自动更新,取值时间复杂度为 O(1)。这和手动调用 Object.keys(obj).length 有本质区别——后者每次都要生成新数组并遍历所有可枚举属性,即使只是想数个数。
常见错误是把 Map 当成“带 size 的 Object”来用,却没意识到它们的语义和使用边界完全不同:
-
Map允许任意类型作 key(包括对象、函数、Symbol),Object的 key 只能是字符串或 Symbol -
Map保留插入顺序,Object在 ES2015+ 中对字符串 key 也保持插入顺序,但对数字 key 仍会排序(如{1: 'a', 10: 'b'}遍历时先输出1再10) -
Map.prototype.size是即时准确的;而Object.keys(obj).length无法反映不可枚举属性或原型链上的属性
对比性能时必须控制变量:只测「纯计数」,不混入序列化或迭代逻辑
很多人测出 “Map 比 Object 慢”,其实是因为写了类似这样的代码:
const obj = { a: 1, b: 2 };
console.time('obj-keys');
console.log(Object.keys(obj).length); // 触发 keys + 数组创建 + length
console.timeEnd('obj-keys');
而 Map 对应写法却是:
const map = new Map([['a', 1], ['b', 2]]);
console.time('map-size');
console.log(map.size); // 单次属性读取
console.timeEnd('map-size');
这种对比不公平。真正公平的 baseline 应该是:
- 测试数据量足够大(比如 10k+ 条目),否则差异被 JS 引擎优化抹平
- 避免在循环中反复调用
Object.keys();如果业务真要频繁统计,应缓存长度或改用 Map - 注意 V8 对小对象有内联缓存优化,
Object.keys({}).length在某些版本下快得异常,不代表通用场景
什么场景下必须用 Map.size,而不是迁就 Object
当你需要动态维护一组键值对,并频繁做「增删 + 查数量」组合操作时,Map 是唯一合理选择。典型例子:
- 实现 LRU 缓存,需在
set时判断是否超限:if (cache.size > MAX_SIZE) {...} - 状态管理中跟踪已订阅的事件监听器数量,用
Map<EventName, Set<Listener>>,靠map.size快速判断是否有监听器 - 服务端路由表存储,key 是路径正则或字符串,value 是 handler,通过
router.size监控注册量
这时候如果硬用 Object 模拟,就得自己维护一个 count 变量,极易出错——比如忘了在 delete obj[key] 后减一,或者误删了原型属性导致计数偏差。
Object.keys().length 不是“慢”,而是“做了多余的事”
Object.keys() 的设计目标从来不是计数,而是获取可枚举属性名列表。它必须:
- 遍历自身所有可枚举属性(不含 Symbol)
- 按规范要求对数字 key 排序(如
'2'排在'10'前) - 构造并返回一个全新数组
所以哪怕你只想要长度,JS 引擎也无法跳过这些步骤。这不是性能缺陷,而是接口契约。如果你只关心数量,又受限于必须用 Object(比如处理后端传来的纯 JSON 数据),可以考虑:
- 用
for...in+hasOwnProperty手动计数(略快于Object.keys,但仍 O(n)) - 提前在数据结构层面约定一个
__count字段(不推荐,破坏数据纯净性) - 接收数据后立刻转成 Map:
const map = new Map(Object.entries(obj)),后续全走map.size
Map.size 的价值不在“多快”,而在于它把一个本应 O(1) 的问题,交还给了 O(1) 的解法。容易被忽略的是:这个属性只有在 Map 实例上存在,WeakMap 没有 size,也不能遍历——别试图用 WeakMap 替代 Map 来“省内存”却还要查数量。


















