JavaScript 实现数组惰性求值:从闭包到 Generator 的两种方案

作者:小括号

时间:2026-10-24

浏览:589

本文详解如何在 JavaScript 中实现数组惰性求值。通过对比基于闭包的函数链与 ES6 Generator 两种方案,解析惰性求值原理。闭包方案利用 Symbol 判断终止,代码简洁但逻辑分散;Generator 方案结合链式调用,结构更清晰。文章提供完整代码示例与测试输出,帮助开发者深入理解惰性计算在性能优化与无限序列构建中的应用。

文章目录

概述

在编程语言理论中,惰性求值(Lazy Evaluation),也译为惰性计算或传需求调用,旨在最小化计算机执行的工作量。它包含“延迟求值”和“最小化求值”两层含义,除了提升性能外,其核心价值在于能够构建无限数据结构。

受函数式语言中惰性求值的启发,本文尝试用 JavaScript 编写最简实现,以加深理解。通过两种方法,均在 80 行代码以内实现了基本的数组惰性求值功能。

实现原理

惰性求值的核心在于:每次求值不直接返回数值,而是返回一个包含计算参数的函数。只有当真正需要使用该值时,才会触发计算过程。

概述

当存在多个惰性操作时,它们会形成一个求值函数链。每次求值时,每个函数会向上游函数请求数据并返回结果。当计算链终止时,最终返回一个终止值。

概述

具体实现细节

判断求值函数终止

由于求值函数可能返回各种数据类型,需使用一个唯一值来标识流是否结束。JavaScript 中的 Symbol() 可以创建与其他任何值都不相等的唯一标识,非常适合用作终止标志。


const over = Symbol();

const isOver = function (_over) {
  return _over === over;
}

生成函数 range

range 函数接收起始和终止参数,返回一个求值函数。执行该函数时,若未达到终止条件则返回当前值,否则返回终止标志。


const range = function (from, to) {
  let i = from;
  return function () {
    if (i < to) {
      i++
      console.log('range\t', i);
      return i
    }
    return over;
  }
}

转换函数 map

map 函数接收一个求值函数和一个处理函数。它从上游求值函数获取数据,应用处理逻辑后,返回一个新的流(即新的求值函数)。


const map = function (flow, transform) {
  return function () {
    const data = flow();
    console.log('map\t', data);
    return isOver(data) ? data : transform(data);
  }
}

过滤函数 filter

filter 函数接收一个求值函数,对上游流中的数据进行过滤。仅当数据符合特定条件时才返回,否则继续从上游请求数据。


const filter = function (flow, condition) {
  return function () {
    while(true) {
      const data = flow();
      if (isOver(data)) {
        return data;
      }
      if(condition(data)) {
        console.log('filter\t', data);
        return data;
      }
    }
  }
}

中断函数 stop

stop 函数接收一个求值函数和一个条件。当满足条件时中断后续计算。结合闭包,stop 可用于实现 take 函数,以限制获取的元素数量。


const stop = function (flow, condition) {
  let _stop = false;
  return function () {
    if (_stop) return over;
    const data = flow();
    if (isOver(data)) {
      return data;
    }
    _stop = condition(data);
    return data;
  }
}

const take = function(flow, num) {
  let i = 0;
  return stop(flow, (data) => {
    return ++i >= num;
  });
}

收集函数 join

由于所有操作均返回函数,最后需要一个 join 函数来遍历整个流,收集所有计算结果并返回一个完整的数组。


const join = function (flow) {
  const array = [];
  while(true) {
    const data = flow();
    if (isOver(data)) {
      break;
    }
    array.push(data);
  }
  return array;
}

测试代码


const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2));
console.log(nums);

输出结果

range 1

map 1

range 2

map 2

range 3

map 3

filter 30

range 4

map 4

range 5

map 5

range 6

map 6

filter 60

更优雅的实现

前文使用函数加闭包实现了惰性求值,但大部分代码用于处理迭代和终止判断,略显繁琐。ES6 引入了 Generator,它能自动处理迭代和流结束判断,让我们能专注于业务逻辑,写出更简洁、结构更清晰的代码。


const range = function* (from, to) {
  for(let i = from; i < to; i++) {
    console.log('range\t', i);
    yield i;
  }
}

const map = function* (flow, transform) {
  for(const data of flow) {
    console.log('map\t', data);
    yield(transform(data));
  }
}

const filter = function* (flow, condition) {
  for(const data of flow) {
    console.log('filter\t', data);
    if (condition(data)) {
      yield data;
    }
  }
}

const stop = function*(flow, condition) {
  for(const data of flow) {
    yield data;
    if (condition(data)) {
      break;
    }
  }
}

const take = function (flow, number) {
  let count = 0;
  const _filter = function (data) {
    count ++
    return count >= number;
  }
  return stop(flow, _filter);
}

为了提升易用性,还需在此基础上添加链式调用支持。


class _Lazy{
  constructor() {
    this.iterator = null;
  }

  range(...args) {
    this.iterator = range(...args);
    return this;
  }

  map(...args) {
    this.iterator = map(this.iterator, ...args);
    return this;
  }

  filter(...args) {
    this.iterator = filter(this.iterator, ...args);
    return this;
  }

  take(...args) {
    this.iterator = take(this.iterator, ...args);
    return this;
  }

  [Symbol.iterator]() {
    return this.iterator;
  }

}

function lazy () {
  return new _Lazy();
}

最终测试代码


const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2);

for(let n of nums) {
  console.log('num:\t', n, '\n');
}

输出结果

range 0

map 0

filter 0

num: 0

range 1

map 1

filter 10

range 2

map 2

filter 20

range 3

map 3

filter 30

num: 30

至此,实现完成。

总结

我们完成了一个最简的数组惰性求值库。虽然仅实现了核心功能,工程化还需补充更多细节,但不足 80 行的代码清晰展示了惰性求值原理,并有助于深入理解 Generator 的使用。

以上就是如何用JavaScript实现一个数组惰性求值库的详细内容,更多关于JavaScript实现数组惰性求值库的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章