摘要:本文不深入分析惰性計算的內部原理后面打算多帶帶做一次分享,而是介紹下我是如何實現上面的回調函數執行計數。問題明確下需求或者說要解決的問題,針對如下的代碼能夠統計代碼執行過程中傳入的回調函數和的實際執行次數。
背景
最近在做一個簡化版的 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,就無法計數。不過這些相對于我要介紹的東西而言,不是那么重要,咱們且得魚忘筌吧。^_^
最后的最后,感謝閱讀!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91247.html
摘要:聯想到我在微信小程序上的開發體驗,真心覺得如果有熱更新機制的話,開發效率要高很多。熱更新示例下面通過例子來進一步解釋熱更新機制。 想必作為前端大佬的你,工作中應該用過 webpack,并且對熱更新的特性也有了解。如果沒有,當然也沒關系。 下面我要講的,是我對 Webpack 熱更新機制的一些認識和理解,不足之處,歡迎指正。 首先: 熱更新是啥? 熱更新,是指 Hot Module Re...
摘要:當組件安裝和更新時,回調函數都會被調用。好在為我們提供了第二個參數,如果第二個參數傳入一個數組,僅當重新渲染時數組中的值發生改變時,中的回調函數才會執行。 前言 首先歡迎大家關注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關注呀!React 16.8中新增了Hooks特性,并且在React官方文檔中新增...
摘要:引用計數會記錄給定對象的引用個數,并在引用個數為零時收集該對象。在對象群組內部使用弱引用即不會在引用計數中被計數的引用有時能避免出現引用環,因此弱引用可用于解決循環引用的問題。 參考 1.weakref – Garbage-collectable references to objects2.Python弱引用介紹 和許多其它的高級語言一樣,Python使用了垃圾回收器來自動銷毀那些不...
摘要:道阻且長啊前端面試總結前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進按鈕書簽目錄瀏覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構建的,使用自主研發的渲染引擎,和都使用網絡用來 道阻且長啊TAT(前端面試總結) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...
閱讀 994·2023-04-25 15:42
閱讀 3584·2021-11-02 14:38
閱讀 2886·2021-09-30 09:48
閱讀 1419·2021-09-23 11:22
閱讀 3379·2021-09-06 15:02
閱讀 3186·2021-09-04 16:41
閱讀 607·2021-09-02 15:41
閱讀 2012·2021-08-26 14:13