首页 / 文章

四个正在被取代常见Lodash辅助函数的JavaScript内置API

Math.sumPrecise、Map.getOrInsert、Array.fromAsync以及迭代器辅助函数如何替代Lodash中的常用工具,以及需要了解的精度和并发方面的注意事项。

发布者:REACTAPP.TOP阅读约 4 分钟888 词

来源: The 4 ES2026 Features That Finally Replace Lodash

过去,在任何 JavaScript 项目中,安装 Lodash 都是首要步骤之一:它可用于求和、用默认值填充映射表以及实现链式延迟转换。不过,最新的 ECMAScript 版本已经提供了这些功能的内置实现,因此引入该依赖往往只会增加包体积。下文介绍了四种原生 API、它们分别替代了什么功能,以及为何通常的一行解释会具有误导性。

浏览器和 Node.js 对这些新特性的支持仍在逐步完善,因此在使用填充代码之前,请先查看相关兼容性数据。如需更全面的了解,可参阅七种可替代实用工具依赖的原生 JavaScript 特性。

使用 Math.sumPrecise() 代替 _.sum()

JavaScript中的数字采用IEEE 754双精度格式,这就是为什么0.1 + 0.2的结果会是0.30000000000000004。_.sum()并未提供任何保护机制:它只是简单地通过循环执行+运算,因此会保留所有四舍五入的步骤。Math.sumPrecise()则接收一个可迭代对象,先以精确计算的方式求和,最后再进行一次四舍五入。

// ❌ The Legacy Lodash/Hack Way
const prices = [0.1, 0.2];
const total = _.sum(prices); // Still prone to precision errors without custom logic


// ✅ The ES2026 Native Standard
const exactTotal = Math.sumPrecise([0.1, 0.2]);
console.log(exactTotal); // 0.3 (Perfect precision)

对上述注释的一个修正:在进行任何加法运算之前,0.1和0.2本身就已经不是精确值,它们的精确总和会四舍五入为0.30000000000000004,因此Math.sumPrecise([0.1, 0.2])返回的是该值而非0.3。此方法的作用是消除多次加法运算中累积的误差:对于[1e20, 0.1, -1e20],普通加法运算的结果为0,而Math.sumPrecise则返回0.1。由于它并非十进制类型,因此处理货币数据时仍应使用整数分或专门的十进制计算库。此外,该方法在遇到非数字类型时会抛出TypeError,而不会尝试将其转换。

使用Map.prototype.getOrInsert()替代has/set/get

缓存和分组辅助函数遵循相同的模式:先检查键是否存在,若不存在则存储默认值,然后再读取该值。getOrInsert(key, defaultValue)可通过一次调用完成所有操作,返回已存在的条目,或插入默认值后将其返回。

// ❌ The Legacy Way
const userCache = new Map();

function getUser(id) {
  if (!userCache.has(id)) {
    userCache.set(id, { status: 'pending' });
  }
  return userCache.get(id);
}




// ✅ The ES2026 Native Standard
const userCache = new Map();

function getUser(id) {
  // Checks for the key. If missing, inserts the fallback and returns it instantly.
  return userCache.getOrInsert(id, { status: 'pending' });
}

请注意,即使该默认对象会被丢弃,每次调用时仍会创建它。如果获取默认值的过程代价较高,可使用getOrInsertComputed(key, callback),该方法仅在键不存在时才会执行回调函数。这两种方法在WeakMap中也有实现;关于Node.js方面的内容可参阅Node.js 26:Temporal、Map upserts与Undici 8。

用于遍历异步可迭代对象的Array.fromAsync()

过去,将分页的 API 客户端或可读取的流收集到数组中,意味着要手动编写 for await...of 循环并将数据逐个推入累加器中。Array.fromAsync() 则会处理异步可迭代对象,依次等待每个元素的处理结果,最终生成一个普通数组。

// The ES2026 Native Standard
async function fetchAllPages(asyncPaginationStream) {
  // Consumes the entire async stream and resolves into a flat array
  const allRecords = await Array.fromAsync(asyncPaginationStream);

  return allRecords;
}

可选的第二个参数可用于对每个元素进行映射处理,其返回结果也会被依次等待,从而实现异步的 map 功能。需要注意的是,元素会按顺序依次被等待处理,这虽然适合流和游标操作,但并不能替代 Promise.all:如果需要同时运行多个独立的承诺,仍应使用 Promise.all。此外,将无限制的流缓存在内存中会违背流式处理的核心理念。

使用迭代器辅助函数而非 _.chain()

继续使用 Lodash 最重要的原因就是 _.chain(),它的惰性求值机制使得对超过 10,000 个元素的映射、过滤和取前五个操作在得到五个结果后就会停止。而数组方法则是即时执行的:.map() 会生成一个完整的中间数组,.filter() 又会遍历这个数组,最后 .slice() 才会丢弃大部分已处理的数据。

ES2025 标准化的迭代器辅助函数让这种惰性求值成为原生支持。.values() 会返回一个迭代器,随后在該迭代器上使用的 map、filter 和 take 方法会逐个提取元素,直到 toArray() 收集完所有结果。

// ❌ The Array Way (Inefficient: Maps and filters all 10,000 items)
const result = massiveArray
  .map(x => heavyComputation(x))
  .filter(x => x > 100)
  .slice(0, 5);



// ✅ The ES2026 Native Lazy Standard
const result = massiveArray
  .values() // Converts to an Iterator
  .map(x => heavyComputation(x))
  .filter(x => x > 100)
  .take(5) // Stops exactly when 5 items are found!
  .toArray();

在请求下一个元素之前,每个元素都会先经过整个处理流程,因此heavyComputation只会运行到找到五个匹配项为止。其代价在于:迭代器为一次性使用,没有懒加载的sort或reverse功能,而对于小型数组来说,即时执行的方法同样快速且更易于调试。

关键要点

  • Math.sumPrecise()可以消除累积的舍入误差,但无法修正不精确的输入数据;处理货币时请使用整数分值。
  • getOrInsert()可替代has/set/get函数;若默认值计算成本较高,建议使用getOrInsertComputed()。
  • Array.fromAsync()会按顺序处理异步可迭代对象;需要并发处理时请使用Promise.all。
  • 结合.values()与迭代器辅助函数,即可实现类似_.chain()的懒加载处理流程,且无需额外依赖。