背景
近来在做一个简化版的 Lazy.js:simply-lazy,目标是深入分析 Lazy.js 中惰性求值的完成,同时由于简化了完成历程,便于在分享(设想近期分享)时作为 demo 展现。
惰性求值的一个主要特征是延迟了盘算历程,从而能够提拔机能,比方:
Lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.map(i => i * 2)
.filter(i => i <= 10)
.take(3)
.each(i => print(i))
注:为了誊写轻易,回调函数运用了 ES 的“=>”来定义。
这里对原始数据实行 map、filter 后只取前 3 个效果值,Lazy.js 的完成战略是 map、filter 中的回调函数也尽量少地被挪用,能够看下统计出的回调函数的挪用次数:
demo 地点:http://www.luobotang.cn/simpl…
注重:须要浏览器环境支撑 ES6 特征,发起运用较新版本的 Chrome 翻开。
从上面的 demo 中能够看到,第三种状况下,虽然依旧要实行与前面雷同的 map、filter 的历程,然则由于终究只须要返回前 3 个效果值,此时 map、filter 的回调函数实行次数是减少了的。
本文不深入分析 Lazy.js 惰性盘算的内部道理(背面盘算零丁做一次分享),而是引见下我是怎样完成上面的回调函数实行计数。
题目
明白下需求或者说要处置惩罚的题目,针对以下的代码:
Lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.map(i => i * 2)
.filter(i => i <= 10)
.take(3)
.each(i => print(i))
能够统计代码实行历程当中 map、filter 传入的回调函数(i => i * 2
和 i => i <= 10
)的现实实行次数。
完成这个需求,能够采纳粗犷的形式,比方:
var mapCount = 0
var filterCount = 0
Lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.map(i => { mapCount++; return i * 2 })
.filter(i => { filterCount++; return i <= 10 })
.take(3)
.each(i => print(i))
console.log('map: ' + mapCount)
console.log('filter: ' + filterCount)
不过如许写的话我的 demo 页面展现的代码不久太丑了吗,计数的历程现实上是分外的事情,写到 demo 代码内里也影响其他人浏览不是吗。
所以,我想完成不修正 demo 代码的计数。
设想
实在在斟酌需求的时刻,就已琢磨过要完成的话得采纳哪些手艺了。比较天然的主意,就是用“假”的 Lazy 函数来替换原有的 Lazy 函数,如许后续的挪用历程就能够举行恣意的 hack 的了。比方:
function FakeLazy(list) {
var seq = Lazy(list)
return {
map() { /* ... */ },
filter() { /* ... */ },
take() { /* ... */ },
each() { /* ... */ }
}
}
貌似是能够的,也应该是能够的,由于后续的挪用现实上是被“挟制”了,我能够把计数的代码添加到回调函数被挪用的时刻实行,比方:
map(fn) {
var subSeq = seq.map(function(e, i){
mapCount++
return fn(e, i)
})
// ...
}
关于 filter 也要实行相似的处置惩罚,而 take、each 则直接挪用原有的 seq 对象上的要领就好了。
别的,由于每次挪用后都邑发生一个新的序列对象(sequence),为了能够一般链接后续的挪用,还要继承返回一个新的挟制的序列对象。有点贫苦,不过也能完成。
能够看到,这类“挟制”对象的历程,比较烦琐,不仅要挟制到体贴的要领,还得保证对象其他的要领也能一般挪用。而在 ES6/ES2015 中,有更好的手艺能够采纳:Proxy – MDN。
Proxy 如许运用:
var proxy = new Proxy(target, handler);
如许能够获得一个代办对象 proxy,与前面的“挟制”对象相似,在顺序中直接运用 proxy 来替换原始的 target 对象。不过 Proxy 对象的壮大的地方在于,关于该代办对象的种种“要求”,会挪用响应的 handler 中传入的回调函数。如许就不须要代办对象完成原始对象的一切功用,只须要处置惩罚那些体贴的状况。
关于前面的状况,运用 Proxy 能够大抵如许处置惩罚:
function FakeLazy(list) {
var seq = Lazy(list)
return Proxy(seq, {
get(target, name) {
if (name === 'map' || name === 'filter') {
// 实行处置惩罚...
} else {
return target[name] // 不须要处置惩罚的状况直接返回原始对象的属性或要领
}
}
})
}
返回的代办对象在被接见任何属性或要领时,都邑被阻拦,起首挪用 handler 中的 get() 要领,如许除了要迥殊处置惩罚的 map 和 filter,其他的直接返回原有属性或要领。
完成
思绪有了,然后就是详细的完成事情了。
起首看下页面处置惩罚逻辑,每一个 demo 代码块我都包装在一个函数中的,然后将实行代码、实行效果、回调计数效果离别输出到页面上,也就是前面图中的那样。
基础历程为:
var demos = [(Lazy, print) => {
Lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.map(i => i * 2)
.filter(i => i <= 10)
.take(3)
.each(i => print(i))
}, (Lazy, print) => {
// ...
}/*, ...*/]
demos.forEach(demoFn => {
var el = document.createElement('div')
var soure // 猎取实行代码...
var result // 猎取实行效果...
var count // 猎取回调计数效果...
el.innerHTML = (
renderSource(soure) +
renderResult(result) +
renderCount(count)
)
document.body.appendChild(el)
})
看到这里,已不耐烦的同砚能够直接去 demo 页面上扒代码来看了,相较于我死板的形貌,代码能够看起来更简朴些。
(1)猎取实行代码
经由过程 demoFn.toString()
就能够了,不过须要分外去除函数定义的头尾部份,只在页面展现实行代码。
(2)猎取实行效果
经由过程传入的 print() 来网络实行效果,也不庞杂:
var output = []
var print = msg => output.push(msg)
然后将 print 函数传入 demoFn 函数,如许代码实行后输出的效果会被网络到 output 中,然后衬着到页面就能够了。
(3)猎取回调计数效果
这个是比较庞杂的部份,对应的完成思绪就是前面讲的了。不过由于 Lazy.js 中每次要领挪用返回的是新的序列对象,要屡次天生代办,所以我将天生代办序列对象的代码零丁抽出:
// 计数对象
var count = {map: 0, filter: 0}
function proxySeq(seq) {
var handler = {
get(target, name) {
// 迥殊处置惩罚 `map` 和 `filter`
if (name === 'map' || name === 'filter') {
// 返回一个能够完成计数的函数
return fn => {
// 这个 fn 是返回的函数被挪用时传入的回调函数,把这个回调
// 函数包装一下再传给原始序列对象的 map 或 filter 要领,
// 从而完成挪用计数
var _fn = (v, i) => {
count[name]++ // 计数
return fn(v, i) // 挪用回调函数
}
// 依旧返回一个新的代办对象
return proxySeq(target[name](_fn))
}
} else {
return target[name]
}
}
}
return new Proxy(seq, handler)
}
经由过程 proxySeq() 来完成一个 FakeLazy:
var _lazy = list => proxySeq(Lazy(list))
和前面的 print 函数一同作为参数来挪用 demoFn 函数从而实行代码:
demoFn(_lazy, print)
实行历程当中能够网络实行效果和回调函数实行次数,这是借助一个个代办对象来“挟制” map、filter 完成的。
衬着的历程的就是字符串拼接了,不再赘述。
小结
代码赛过万语千言,感兴趣的同砚能够去读一下 demo 页面的源码。
末了叹息一下,Lazy.js 的完成照样蛮有意思的,以后我会连系 simply-lazy 分享下惰性求值的完成道理。对了,demo 页面运用的现实上是 simply-lazy,而非 Lazy.js。
实在无论是 simply-lazy 照样这里 demo 页面的完成,都有一些不足,比方 demo 页面中实在没有处置惩罚 take(),如许后续假如再挪用 map 或 filter,就没法计数。不过这些相关于我要引见的东西而言,不是那末主要,我们且得鱼忘筌吧。^_^
末了的末了,谢谢浏览!