如何用JavaScript实现一个数组惰性求值库


Posted in Javascript onMay 05, 2021

概述

在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。

看到函数式语言里面的惰性求值,想自己用JavaScript写一个最简实现,加深对惰性求值了解。用了两种方法,都不到 80 行实现了基本的数组的惰性求值。

怎么实现

惰性求值每次求值的时候并不是返回数值,而是返回一个包含计算参数的求值函数,每次到了要使用值得时候,才会进行计算。

如何用JavaScript实现一个数组惰性求值库

当有多个惰性操作的时候,构成一个求值函数链,每次求值的时候,每个求值函数都向上一个求值函数求值,返回一个值。最后当计算函数终止的时候,返回一个终止值。

如何用JavaScript实现一个数组惰性求值库

具体实现

判断求值函数终止

每次求值函数都会返回各种数据,所以得使用一个独一无二的值来作为判断流是否完成的标志。刚好 Symbol() 可以创建一个新的 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

接受一个求值函数和处理函数,获取求值函数 flow 中的数据,对数据进行处理,返回一个流。

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

过滤函数 filter

接受一个求值函数,对求值函数 flow 中数据进行过滤,找到符合的数据并且返回。

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 函数接着实现一个 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,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 行,可以很清楚的了解惰性求值原理,还能加深对生成器的理解。

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

Javascript 相关文章推荐
JavaScript与DropDownList 区别分析
Jan 01 Javascript
JQUERY操作JSON实例代码
Feb 09 Javascript
LazyLoad 延迟加载(按需加载)
May 31 Javascript
js实现一个省市区三级联动选择框代码分享
Mar 06 Javascript
在JavaScript中使用开平方根的sqrt()方法
Jun 15 Javascript
javascript实现在网页中运行本地程序的方法
Feb 03 Javascript
Bootstrap组件系列之福利篇几款好用的组件(推荐二)
Jul 12 Javascript
AngularJS中的JSONP实例解析
Dec 01 Javascript
jQuery ajax的功能实现方法详解
Jan 06 Javascript
前端开发之CSS原理详解
Mar 11 Javascript
node.js(express)中使用Jcrop进行图片剪切上传功能
Apr 21 Javascript
Vue中的验证登录状态的实现方法
Mar 09 Javascript
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
Vue项目中如何封装axios(统一管理http请求)
May 02 #Vue.js
如何用JavaScript学习算法复杂度
JS不要再到处使用绝对等于运算符了
Apr 30 #Javascript
如何用Node.js编写内存效率高的应用程序
You might like
叶罗丽:为什么大家对颜冰这对CP非常关心,却对金茉两人十分冷漠
2020/03/17 国漫
解决PHP超大文件下载,断点续传下载的方法详解
2013/06/06 PHP
PHP用continue跳过本次循环中剩余代码的注意点
2017/06/27 PHP
实例讲解PHP验证邮箱是否合格
2019/01/28 PHP
php中get_object_vars()在数组的实例用法
2021/02/22 PHP
js ondocumentready onmouseover onclick onmouseout 样式
2010/07/22 Javascript
用js写了一个类似php的print_r输出换行功能
2013/02/18 Javascript
Jquery读取URL参数小例子
2013/08/30 Javascript
实现网页页面跳转的几种方法(meta标签、js实现、php实现)
2014/05/20 Javascript
Javascript学习指南
2014/12/01 Javascript
使用js画图之饼图
2015/01/12 Javascript
javascript设计模式--策略模式之输入验证
2015/11/27 Javascript
Bootstrap入门书籍之(零)Bootstrap简介
2016/02/17 Javascript
Angular中$cacheFactory的作用和用法实例详解
2016/08/19 Javascript
d3.js实现简单的网络拓扑图实例代码
2016/11/06 Javascript
webpack学习笔记之代码分割和按需加载的实例详解
2017/07/20 Javascript
在原生不支持的旧环境中添加兼容的Object.keys实现方法
2017/09/11 Javascript
微信小程序用户自定义模版用法实例分析
2017/11/28 Javascript
jQuery实现遍历XML节点和属性的方法示例
2018/04/29 jQuery
详解React 服务端渲染方案完美的解决方案
2018/12/14 Javascript
从0到1搭建Element的后台框架的方法步骤
2019/04/10 Javascript
Vue组件系列开发之模态框
2019/04/18 Javascript
Python实现线程状态监测简单示例
2018/03/28 Python
深入浅析Python中list的复制及深拷贝与浅拷贝
2018/09/03 Python
关于PyTorch源码解读之torchvision.models
2019/08/17 Python
基于python的BP神经网络及异或实现过程解析
2019/09/30 Python
Python:合并两个numpy矩阵的实现
2019/12/02 Python
解决TensorFlow训练内存不断增长,进程被杀死问题
2020/02/05 Python
使用TFRecord存取多个数据案例
2020/02/17 Python
Python高并发和多线程有什么关系
2020/11/14 Python
英国在线药房:Chemist.co.uk
2019/03/26 全球购物
Wallis官网:英国女装零售商
2020/01/21 全球购物
门卫岗位职责说明书
2014/08/18 职场文书
2014年大学教师工作总结
2014/12/02 职场文书
老干部局2015年度工作总结
2015/10/22 职场文书
2016年小学端午节活动总结
2016/04/01 职场文书