如何用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 相关文章推荐
将函数的实际参数转换成数组的方法
Jan 25 Javascript
jQuery autocomplate 自扩展插件、自动完成示例代码
Mar 28 Javascript
网站接入QQ登录的两种方法
Jul 22 Javascript
一个实用的图片切换支持点击切换和自动轮播
Sep 09 Javascript
javascript实现自动填写表单实例简析
Dec 02 Javascript
神奇!js+CSS+DIV实现文字颜色渐变效果
Mar 16 Javascript
jQuery实现验证码功能
Mar 17 Javascript
bmob js-sdk 在vue中的使用教程
Jan 21 Javascript
Angular服务Request异步请求的实例讲解
Aug 13 Javascript
layer.open回调获取弹出层参数的实现方法
Sep 10 Javascript
使用Karma做vue组件单元测试的实现
Jan 16 Javascript
Vue在H5 项目中使用融云进行实时个人单聊通讯
Dec 14 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
如何将一个表单同时提交到两个地方处理
2006/10/09 PHP
php jquery 实现新闻标签分类与无刷新分页
2009/12/18 PHP
修改php.ini不生效问题解决方法(上传大于8M的文件)
2013/06/14 PHP
php使用redis的几种常见操作方式和用法示例
2020/02/20 PHP
javascript各种复制代码收集
2008/09/20 Javascript
js 时间函数应用加、减、比较、格式转换的示例代码
2013/08/23 Javascript
JSON+HTML实现国家省市联动选择效果
2014/05/18 Javascript
javascript实现给定半径求出圆的面积
2015/06/26 Javascript
php利用curl获取远程图片实现方法
2015/10/26 Javascript
写给小白的JavaScript引擎指南
2015/12/04 Javascript
全面接触神奇的Bootstrap导航条实战篇
2016/08/01 Javascript
基于jQuery实现的幻灯图片切换
2016/12/02 Javascript
jquery表单验证插件validation使用方法详解
2017/01/20 Javascript
jQuery实现图片滑动效果
2017/03/08 Javascript
angularjs的select使用及默认选中设置
2017/04/08 Javascript
使用vue2实现购物车和地址选配功能
2018/03/29 Javascript
详解Webpack + ES6 最新环境搭建与配置
2018/06/04 Javascript
JavaScript ES6常用基础知识总结
2019/02/09 Javascript
详细分析vue表单数据的绑定
2020/07/20 Javascript
[02:28]DOTA2英雄基础教程 狼人
2013/12/23 DOTA
python判断字符串是否纯数字的方法
2014/11/19 Python
Python中的迭代器漫谈
2015/02/03 Python
python实现逆序输出一个数字的示例讲解
2018/06/25 Python
pytorch 归一化与反归一化实例
2019/12/31 Python
pytorch 利用lstm做mnist手写数字识别分类的实例
2020/01/10 Python
Html5中的桌面通知Notification的实现
2018/09/25 HTML / CSS
美国综合购物商城:UnbeatableSale.com
2018/11/28 全球购物
劳动工资科岗位职责范本
2014/03/02 职场文书
留学生求职信
2014/06/03 职场文书
ktv好的活动方案
2014/08/15 职场文书
住房租房协议书
2014/08/20 职场文书
工作作风建设心得体会
2014/10/22 职场文书
党员公开承诺书2015
2015/01/21 职场文书
2015秋季小学开学寄语
2015/05/27 职场文书
国富论读书笔记
2015/06/26 职场文书
任命书格式范文
2015/09/22 职场文书