借助 Proxy 完成回调函数实行计数

背景

近来在做一个简化版的 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 中的回调函数也尽量少地被挪用,能够看下统计出的回调函数的挪用次数:

《借助 Proxy 完成回调函数实行计数》

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 * 2i => 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,就没法计数。不过这些相关于我要引见的东西而言,不是那末主要,我们且得鱼忘筌吧。^_^

末了的末了,谢谢浏览!

    原文作者:luobotang
    原文地址: https://segmentfault.com/a/1190000007686565
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞