如何用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 相关文章推荐
Array.slice()与Array.splice()的返回值类型
Oct 09 Javascript
Javascript 中的 &amp;&amp; 和 || 使用小结
Apr 25 Javascript
在firefox和Chrome下关闭浏览器窗口无效的解决方法
Jan 16 Javascript
javascript生成随机数的方法
May 16 Javascript
浅谈jQuery 中的事件冒泡和阻止默认行为
May 28 Javascript
jQuery事件委托之Safari
Jul 05 Javascript
Vue.js 递归组件实现树形菜单(实例分享)
Dec 21 Javascript
JS实现图片高斯模糊切换效果的焦点图实例
Jan 21 Javascript
vue如何实现observer和watcher源码解析
Mar 09 Javascript
vue实现点击展开点击收起效果
Apr 27 Javascript
Vue中this.$nextTick的作用及用法
Feb 04 Javascript
解决vue项目打包上服务器显示404错误,本地没出错的问题
Nov 03 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
php使用curl模拟登录后采集页面的例子
2013/11/04 PHP
PHP goto语句简介和使用实例
2014/03/11 PHP
JavaScript使用IEEE 标准进行二进制浮点运算产生莫名错误的解决方法
2011/05/28 Javascript
js 浏览器事件介绍
2012/03/30 Javascript
js关于精确计算和数值格式化以及直接引js文件
2014/01/28 Javascript
引入autocomplete组件时JS报未结束字符串常量错误
2014/03/19 Javascript
document.compatMode的CSS1compat使用介绍
2014/04/03 Javascript
jQuery+CSS3实现3D立方体旋转效果
2015/11/10 Javascript
理解js回收机制通俗易懂版
2016/02/29 Javascript
一波JavaScript日期判断脚本分享
2016/03/06 Javascript
JavaScript实现三级联动菜单实例代码
2017/06/26 Javascript
详解mpvue中小程序自定义导航组件开发指南
2019/02/11 Javascript
深入探索VueJS Scoped CSS 实现原理
2019/09/23 Javascript
如何在JavaScript中正确处理变量
2020/12/25 Javascript
vue.js实现点击图标放大离开时缩小的代码
2021/01/27 Vue.js
[00:38]TI珍贵瞬间系列(二):笑
2020/08/26 DOTA
wxPython窗口的继承机制实例分析
2014/09/28 Python
python简单判断序列是否为空的方法
2015/06/30 Python
python利用datetime模块计算时间差
2015/08/04 Python
在python3环境下的Django中使用MySQL数据库的实例
2017/08/29 Python
python利用JMeter测试Tornado的多线程
2020/01/12 Python
Css3新特性应用之视觉效果实例
2016/12/12 HTML / CSS
英国最大的电脑零售连锁店集团:PC World
2016/10/10 全球购物
泰国汽车、火车和轮渡票预订网站:Bus Online Ticket
2017/09/09 全球购物
The North Face北面英国官网:美国著名户外品牌
2017/12/13 全球购物
德国旅行、体验和活动的预订平台:Watado
2019/12/04 全球购物
营销与策划应届生求职信
2013/11/04 职场文书
自荐信格式简述
2014/01/25 职场文书
行政文秘岗位职责范本
2014/02/10 职场文书
公司副总经理任命书
2014/06/05 职场文书
普通话演讲稿
2014/09/03 职场文书
2015年社区妇联工作总结
2015/04/21 职场文书
2015年度考核个人工作总结
2015/10/24 职场文书
Golang 空map和未初始化map的注意事项说明
2021/04/29 Golang
一篇文章带你搞懂Python类的相关知识
2021/05/20 Python
解决IDEA翻译插件Translation报错更新TTK失败不能使用
2022/04/24 Python