如何用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 相关文章推荐
jquery.lazyload  实现图片延迟加载jquery插件
Feb 06 Javascript
EASYUI TREEGRID异步加载数据实现方法
Aug 22 Javascript
jquery动态添加删除一行数据示例
Jun 12 Javascript
JQ技术实现注册页面带有校验密码强度
Jul 27 Javascript
jQuery实现可关闭固定于底(顶)部的工具条菜单效果
Nov 06 Javascript
jQuery轻松实现表格的隔行变色和点击行变色的实例代码
May 09 Javascript
Active控件问题小结(附解决办法)
Jun 09 Javascript
Bootstrap php制作动态分页标签
Dec 23 Javascript
jQuery模拟实现天猫购物车动画效果实例代码
May 25 jQuery
通俗解释JavaScript正则表达式快速记忆
Aug 23 Javascript
AngularJS与BootStrap模仿百度分页的示例代码
May 23 Javascript
Vue3 实现双盒子定位Overlay的示例
Dec 22 Vue.js
原生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
php access 数据连接与读取保存编辑数据的实现代码
2010/05/12 PHP
php使用exec shell命令注入的方法讲解
2013/11/12 PHP
PHP中常用的三种设计模式详解【单例模式、工厂模式、观察者模式】
2019/06/14 PHP
thinkPHP5框架路由常用知识点汇总
2019/09/15 PHP
yii 框架实现按天,月,年,自定义时间段统计数据的方法分析
2020/04/04 PHP
php实现商城购物车的思路和源码分析
2020/07/23 PHP
jquery清空textarea等输入框实现代码
2013/04/22 Javascript
JavaScript 判断一个对象{}是否为空对象的简单方法
2016/10/09 Javascript
jQuery EasyUI之验证框validatebox实例详解
2017/04/10 jQuery
详解Vue.js基于$.ajax获取数据并与组件的data绑定
2017/05/26 Javascript
BootStrap点击保存后实现模态框自动关闭的思路(模态框)
2017/09/26 Javascript
Easy UI动态树点击文字实现展开关闭功能
2017/09/30 Javascript
详解Vue Elememt-UI构建管理后台
2018/02/27 Javascript
搭建vue开发环境
2018/07/19 Javascript
layui的表单验证支持ajax判断用户名是否重复的实例
2019/09/06 Javascript
vue实现弹幕功能
2019/10/25 Javascript
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
2020/04/25 Javascript
vue使用nprogress加载路由进度条的方法
2020/06/04 Javascript
python将MongoDB里的ObjectId转换为时间戳的方法
2015/03/13 Python
Python实现国外赌场热门游戏Craps(双骰子)
2015/03/31 Python
python使用calendar输出指定年份全年日历的方法
2015/04/04 Python
简单总结Python中序列与字典的相同和不同之处
2016/01/19 Python
Python cookbook(数据结构与算法)实现查找两个字典相同点的方法
2018/02/18 Python
Python 实现在文件中的每一行添加一个逗号
2018/04/29 Python
python实现字符串加密 生成唯一固定长度字符串
2019/03/22 Python
使用CSS3的appearance属性改变元素的外观的方法
2015/12/12 HTML / CSS
h5页面背景图很长要有滚动条滑动效果的实现
2021/01/27 HTML / CSS
体育纪念品、亲笔签名的体育收藏品:Steiner Sports
2020/07/31 全球购物
质检部职责
2013/12/28 职场文书
获奖的大学生创业计划书
2014/01/05 职场文书
中学教师培训制度
2014/01/31 职场文书
房地产活动策划方案
2014/05/14 职场文书
党员干部反四风对照检查材料思想汇报
2014/09/14 职场文书
2015年重阳节主持词
2015/07/04 职场文书
2016年党员岗位承诺书
2016/03/24 职场文书
经典《舰娘》游改全新动画预告 预定11月开播
2022/04/01 日漫