在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。
看到函数式语言里面的惰性求值,想自己用 JavaScript 写一个最简实现,加深对惰性求值了解。用了两种方法,都不到 80 行实现了基本的数组的惰性求值。
惰性求值每次求值的时候并不是返回数值,而是返回一个包含计算参数的求值函数,每次到了要使用值得时候,才会进行计算。
当有多个惰性操作的时候,构成一个求值函数链,每次求值的时候,每个求值函数都向上一个求值函数求值,返回一个值。最后当计算函数终止的时候,返回一个终止值。
判断求值函数终止
每次求值函数都会返回各种数据,所以得使用一个独一无二的值来作为判断流是否完成的标志。刚好 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
[ 30, 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 行,可以很清楚的了解惰性求值原理,还能加深对生成器的理解。
最后这里是 github 地址
avaScript数组同后端语言一样,具有它自己的数据结构,归根结底,这种数据结构,本质就是一种集合。在后端语言中(如java,.net等),数组是这样定义的:数组是用来存储相同数据类型的集合
单个数字转汉字的解决方法:利用数组存储0-9的汉字、 ary.length和str.length不用多说,这是指ary数组和str字符串的长度。这里我们需要注意的是str.charAt(j)和ary[i],分别指在str这个字符串中索引为j的元素,在ary中索引为i的元素。
在 Javascript 循环中使用 async/ await 循环遍历数组似乎很简单,但是在将两者结合使用时需要注意一些非直观的行为。让我们看看三个不同的例子,看看你应该注意什么,以及哪个循环最适合特定用例。
今天说的数组和字符串去重呢,主要用到es6新的数据结构 Set,它类似于数组,但是成员的值都是唯一的,没有重复的值,所以活用Set来进行数组和字符串的去重。
数组方法:1、Array.join([param]) 方法:将数组中所有的元素都转换为字符串并连接起来,通过字符 param 连接,默认使用逗号,返回最后生成的字符串2、Array.reverse() 方法:将数组中的元素颠倒顺序(在原数组中重新排列它们),返回逆序数组
falsy(虚值)是在 Boolean 上下文中已认定可转换为‘假‘的值.JavaScript 在需要用到布尔类型值的上下文中使用强制类型转换(Type Conversion )将值转换为布尔值,比如:在条件语句或者循环语句中。
JavaScript中我们经常会遇到拷贝数组的场景,但是都有哪些方式能够来实现呢,我们不妨来梳理一下。扩展运算符(浅拷贝)自从ES6出现以来,这已经成为最流行的方法。
本文主要来讲数组api的一些操作,如简单实现扁平化n维数组、数组去重、求数组最大值、数组求和、排序、对象和数组的转化等。扁平化嵌套数组/展平和阵列孔——flat()
vue无法监听数组变化的情况,但是数组在下面两种情况下无法监听:利用索引直接设置数组项时,例如arr[indexofitem]=newValue;修改数组的长度时,例如arr.length=newLength
使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本,使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本。也不用引入其他第三方库。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!