JavaScript惰性求值的一种实现方法示例
本文介绍如何在JavaScript中通过Iterator和Generator实现惰性求值,模拟Haskell的高阶函数行为。通过封装map、filter和takeWhile方法,构建Lazy对象,实现按需计算,避免生成无限列表,提升代码表达性。
引言:从 Haskell 到 JavaScript 的惰性求值
前言
在探究 Haskell 语言特性时,观察到一种特定的编程范式:
sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
该代码旨在计算小于 10000 且同时为奇数与完全平方数的自然数之和。得益于 Haskell 的惰性求值机制,程序不会立即构建从 1 到无穷的完整列表,而是依据 takeWhile 的约束动态生成数据。若使用 JavaScript 实现同等逻辑,代码往往缺乏 Haskell 的简洁性与高表达力。一种直观的替代方案是编写显式的 while 循环进行累加,此处略去演示。
然而,若试图通过高阶函数模拟 Haskell 的行为,必须解决惰性求值的问题。实现惰性的核心机制在于 Iterator。只要不主动调用 next() 方法,迭代器将保持静止状态。
接下来,我们将利用 Iterator 构建惰性求值系统。
首先,定义一个生成从 1 开始无穷自然数的 generator:
实现惰性求值的核心:Iterator 与 Generator
const numbers = function*() {
let i = 1
while (true) {
yield i++
}
}
关键点在于,只有当对 generator 生成的 iterable 调用 next() 时,yield 语句才会执行。因此,核心任务在于定制不同的 next 方法以实现预期功能。
我们需要构建一个工厂函数 Lazy,用于封装各类目标操作:
构建 Lazy 工厂与高阶操作:map、filter 和 takeWhile
const Lazy = iterator => {
const next = iterable.next.bind(iterable)
const map = () => {}
const filter = () => {}
const takeWhile = () => {}
return {
next,
map,
filter,
takeWhile,
}
首先实现 map 方法,该方法依据提供的回调函数,对每次 next 返回的值进行转换:
const map = f => {
const modifiedNext = () => {
const item = next()
const mappedValue = f(item.value)
return {
value: mappedValue,
done: item.done,
}
}
const newIter = { ...iterable, next: modifiedNext }
return lazy(newIter)
}
接着定义 filter 方法,确保 next 仅返回满足特定判断条件的值:
const filter = predicate => {
const modifiedNext = () => {
while (true) {
const item = next()
if (predicate(item.value)) {
return item
}
}
}
const newIter = { ...iterable, next: modifiedNext }
return lazy(newIter)
}
最后,定义 takeWhile 方法,该方法限制 next 的执行条件。一旦条件不再满足,则停止执行 next 并返回累积的历史结果:
const takeWhile = predicate => {
const result = []
let value = next().value
while (predicate(value)) {
result.push(value)
value = next().value
}
return result
}
核心方法定义完毕后,将其整合:
const Lazy = iterable => {
const next = iterable.next.bind(iterable)
const map = f => {
const modifiedNext = () => {
const item = next()
const mappedValue = f(item.value)
return {
value: mappedValue,
done: item.done,
}
}
const newIter = { ...iterable, next: modifiedNext }
return lazy(newIter)
}
const filter = predicate => {
const modifiedNext = () => {
while (true) {
const item = next()
if (predicate(item.value)) {
return item
}
}
}
const newIter = { ...iterable, next: modifiedNext }
return lazy(newIter)
}
const takeWhile = predicate => {
const result = []
let value = next().value
while (predicate(value)) {
result.push(value)
value = next().value
}
return result
}
return Object.freeze({
map,
filter,
takeWhile,
next,
})
}
const numbers = function*() {
let i = 1
while (true) {
yield i++
}
}
利用我们构建的 Lazy 工具及 numbers 函数,复现文章开头的 Haskell 代码:
完整示例:模拟 Haskell 代码
Lazy(numbers())
.map(x => x ** 2)
.filter(x => x % 2 === 1)
.takeWhile(x => x < 10000)
.reduce((x, y) => x + y)
// => 16650
参考:
Lazy Evaluation in JavaScript with Generators, Map, Filter, and Reduce
总结
本文阐述了上述全部内容,期望对读者的学习或工作有所助益。若有任何疑问,欢迎留言交流,感谢支持本站。
JS合并多个数组的三种方法:concat、spread与flat
2026-11-05 17:39:45
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
