在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。
因为在JS中,表达式是会被立即计算的,所以要实现惰性求值,只能返回一个方法,等到调用到这个返回方法的时候再去求值,所以range方法大概应该长成这样:
function range (from, to) {
var i = from
return function () {
if (i++ < to) {
console.log('range\t', i)
return i
}
}
}
还有一需要解决的问题是如何表示结束,上面的方法中i超过边界久没有返回值的这显然是不行的,我们需要一个唯一值,当返回这个唯一值的时候表示迭代结束了,我们利用ES6新增的第七个基本类型可以做到:
var over = Symbol()
function isOver (_over) {
return _over === over
}
function range (from, to) {
var i = from
return function () {
if (i++ < to) {
console.log('range\t', i)
return i
}
return over
}
}
然后我们在后面的处理方法里只要判断isOver就可以知道是否结束了,比如map方法,如果迭代没有结束就把当前的计算值作为参数传给mapFn,否则就返回返回当前的计算值:
function map (flow, transform) {
return function () {
var data = flow()
console.log('map\t', data)
return isOver(data) ? data : transform(data)
}
}
filter和take方法也是类似的思路,join方法有一点不一样,join方法负责收集所有的结果返回一个数组:
function join (flow) {
const array = []
while (true) {
var data = flow()
if (isOver(data)) {
break
}
array.push(data)
}
return array
}
目前基本功能都实现了,调用一下:
cosnt nums = join(take(filter(map(range(0, 20), n => n*10), n => n%3 === 0), 2))
console.log(nums)
/*
output:
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 ]
*/
可以看到,当range返回一个值后立刻被map处理了,并不是range处理了所有的元素才到map,而且,当take数量足够的时候即使结束了迭代,避免了不必要的便利,这点在处理体量大的数组的时候可以节省许多性能。
但是,看起来太丑了,一点都没有lazyjs和lodash那样优雅,我们需要用一个封装来实现链式调用:
function _Lazy() {
this.iterator = null
this.range = function (from, to) {
this.iterator = range(from, to)
return this
}
this.map = function(mapFn) {
this.iterator = map(this.iterator, mapFn)
return this
}
this.filter = function (filterFn) {
this.iterator = filter(this.iterator, filterFn)
return this
}
this.take = function (n) {
this.iterator = take(this.iterator, n)
return this
}
this.join = function () {
return join(this.iterator)
}
}
function lazy() {
return new _Lazy()
}
这时调用就变成这样,好看多了逼格也高:
const nums2 = lazy().range(0, 20).map(n => n*10).filter(n => n%3 === 0).take(2).join()
console.log(nums2)
惰性求值使用了函数式编程的思想,对于我们这种OOP语言出身的程序猿在这一点上转变是很别扭的,但是目前越来越多的库在推崇这种编程思想,前段时间看RxJS时深有体会,虽然日常实现的业务场景可能暂时不会有这样的需求,但是人嘛,开心最重要,多折腾折腾可以延缓衰老
:)