如何用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 国际象棋棋盘 实现代码
Jun 26 Javascript
JS防止用户多次提交的简单代码
Aug 01 Javascript
javascript中数组的sort()方法的使用介绍
Dec 18 Javascript
JavaScript和JQuery的鼠标mouse事件冒泡处理
Jun 19 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
Oct 20 Javascript
angularjs 表单密码验证自定义指令实现代码
Oct 27 Javascript
webpack+react+antd脚手架优化的方法
Apr 02 Javascript
关于TypeScript模块导入的那些事
Jun 12 Javascript
脚手架vue-cli工程webpack的作用和特点
Sep 29 Javascript
微信小程序实现订单倒计时
Nov 01 Javascript
基于Angular 8和Bootstrap 4实现动态主题切换的示例代码
Feb 11 Javascript
浅谈vue的第一个commit分析
Jun 08 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
MySQL连接数超过限制的解决方法
2011/07/17 PHP
使用php将某个目录下面的所有文件罗列出来的方法详解
2013/06/21 PHP
PHP Session 变量的使用方法详解与实例代码
2013/09/11 PHP
用php来限制每个ip每天浏览页面数量的实现思路
2015/02/24 PHP
php中 ob_start等函数截取标准输出的方法
2015/06/22 PHP
thinkPHP多域名情况下使用memcache方式共享session数据的实现方法
2016/07/21 PHP
jQuery 相关控件的事件操作分解
2009/08/03 Javascript
jQuery帮助之CSS尺寸(五)outerHeight、outerWidth
2009/11/14 Javascript
jQuery中removeAttr()方法用法实例
2015/01/05 Javascript
简单实现JS对dom操作封装
2015/12/02 Javascript
手机注册发送验证码倒计时的简单实例
2017/11/15 Javascript
浅谈jquery fullpage 插件增加头部和版权的方法
2018/03/20 jQuery
Vue通过ref父子组件拿值方法
2018/09/12 Javascript
详解Vue实战指南之依赖注入(provide/inject)
2018/11/13 Javascript
jQuery表单选择器用法详解
2019/08/22 jQuery
Vue.js暴露方法给WebView的使用操作
2020/09/07 Javascript
python处理中文编码和判断编码示例
2014/02/26 Python
python为tornado添加recaptcha验证码功能
2014/02/26 Python
centos6.7安装python2.7.11的具体方法
2017/01/16 Python
Python通过OpenCV的findContours获取轮廓并切割实例
2018/01/05 Python
python 时间信息“2018-02-04 18:23:35“ 解析成字典形式的结果代码详解
2018/04/19 Python
Python工厂函数用法实例分析
2018/05/14 Python
浅析python继承与多重继承
2018/09/13 Python
Python 获取numpy.array索引值的实例
2019/12/06 Python
python 实现让字典的value 成为列表
2019/12/16 Python
python通过matplotlib生成复合饼图
2020/02/06 Python
Python发起请求提示UnicodeEncodeError错误代码解决方法
2020/04/21 Python
PyCharm设置Ipython交互环境和宏快捷键进行数据分析图文详解
2020/04/23 Python
滞留工资返还协议书
2014/10/19 职场文书
趵突泉导游词
2015/02/03 职场文书
教师个人年终总结
2015/02/11 职场文书
酒店客房服务员岗位职责
2015/04/09 职场文书
毕业实习单位意见
2015/06/04 职场文书
导游词之平津战役纪念馆
2019/11/04 职场文书
python自然语言处理之字典树知识总结
2021/04/25 Python
golang gopm get -g -v 无法获取第三方库的解决方案
2021/05/05 Golang