摘要:本文不深入分析惰性計算的內(nèi)部原理后面打算多帶帶做一次分享,而是介紹下我是如何實現(xiàn)上面的回調(diào)函數(shù)執(zhí)行計數(shù)。問題明確下需求或者說要解決的問題,針對如下的代碼能夠統(tǒng)計代碼執(zhí)行過程中傳入的回調(diào)函數(shù)和的實際執(zhí)行次數(shù)。
背景
最近在做一個簡化版的 Lazy.js:simply-lazy,目的是深入分析 Lazy.js 中惰性求值的實現(xiàn),同時由于簡化了實現(xiàn)過程,便于在分享(計劃近期分享)時作為 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))
注:為了書寫方便,回調(diào)函數(shù)使用了 ES 的“=>”來定義。
這里對原始數(shù)據(jù)執(zhí)行 map、filter 后只取前 3 個結(jié)果值,Lazy.js 的實現(xiàn)策略是 map、filter 中的回調(diào)函數(shù)也盡可能少地被調(diào)用,可以看下統(tǒng)計出的回調(diào)函數(shù)的調(diào)用次數(shù):
demo 地址:http://www.luobotang.cn/simpl...
注意:需要瀏覽器環(huán)境支持 ES6 特性,建議使用較新版本的 Chrome 打開。
從上面的 demo 中可以看到,第三種情況下,雖然仍舊要執(zhí)行與前面相同的 map、filter 的過程,但是由于最終只需要返回前 3 個結(jié)果值,此時 map、filter 的回調(diào)函數(shù)執(zhí)行次數(shù)是減少了的。
本文不深入分析 Lazy.js 惰性計算的內(nèi)部原理(后面打算多帶帶做一次分享),而是介紹下我是如何實現(xiàn)上面的回調(diào)函數(shù)執(zhí)行計數(shù)。
問題明確下需求或者說要解決的問題,針對如下的代碼:
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))
能夠統(tǒng)計代碼執(zhí)行過程中 map、filter 傳入的回調(diào)函數(shù)(i => i * 2 和 i => i <= 10)的實際執(zhí)行次數(shù)。
實現(xiàn)這個需求,可以采用粗暴的模式,例如:
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 頁面展示的代碼不久太丑了嗎,計數(shù)的過程其實是額外的工作,寫到 demo 代碼里面也影響其他人閱讀不是嗎。
所以,我想實現(xiàn)不修改 demo 代碼的計數(shù)。
設(shè)計其實在考慮需求的時候,就已經(jīng)琢磨過要實現(xiàn)的話得采用哪些技術(shù)了。比較自然的想法,就是用“假”的 Lazy 函數(shù)來替換原有的 Lazy 函數(shù),這樣后續(xù)的調(diào)用過程就可以進行任意的 hack 的了。例如:
function FakeLazy(list) { var seq = Lazy(list) return { map() { /* ... */ }, filter() { /* ... */ }, take() { /* ... */ }, each() { /* ... */ } } }
貌似是可以的,也應(yīng)該是可以的,因為后續(xù)的調(diào)用實際上是被“劫持”了,我可以把計數(shù)的代碼添加到回調(diào)函數(shù)被調(diào)用的時候執(zhí)行,例如:
map(fn) { var subSeq = seq.map(function(e, i){ mapCount++ return fn(e, i) }) // ... }
對于 filter 也要執(zhí)行類似的處理,而 take、each 則直接調(diào)用原有的 seq 對象上的方法就好了。
另外,由于每次調(diào)用后都會產(chǎn)生一個新的序列對象(sequence),為了能夠正常鏈接后續(xù)的調(diào)用,還要繼續(xù)返回一個新的劫持的序列對象。有點麻煩,不過也能實現(xiàn)。
可以看到,這種“劫持”對象的過程,比較繁瑣,不僅要劫持到關(guān)心的方法,還得保證對象其他的方法也能正常調(diào)用。而在 ES6/ES2015 中,有更好的技術(shù)可以采用:Proxy - MDN。
Proxy 這樣使用:
var proxy = new Proxy(target, handler);
這樣可以得到一個代理對象 proxy,與前面的“劫持”對象類似,在程序中直接使用 proxy 來替代原始的 target 對象。不過 Proxy 對象的強大之處在于,對于該代理對象的各種“請求”,會調(diào)用相應(yīng)的 handler 中傳入的回調(diào)函數(shù)。這樣就不需要代理對象實現(xiàn)原始對象的所有功能,只需要處理那些關(guān)心的情況。
對于前面的情況,使用 Proxy 可以大致這樣處理:
function FakeLazy(list) { var seq = Lazy(list) return Proxy(seq, { get(target, name) { if (name === "map" || name === "filter") { // 執(zhí)行處理... } else { return target[name] // 不需要處理的情況直接返回原始對象的屬性或方法 } } }) }
返回的代理對象在被訪問任何屬性或方法時,都會被攔截,首先調(diào)用 handler 中的 get() 方法,這樣除了要特殊處理的 map 和 filter,其他的直接返回原有屬性或方法。
實現(xiàn)思路有了,然后就是具體的實現(xiàn)工作了。
首先看下頁面處理邏輯,每個 demo 代碼塊我都包裝在一個函數(shù)中的,然后將執(zhí)行代碼、執(zhí)行結(jié)果、回調(diào)計數(shù)結(jié)果分別輸出到頁面上,也就是前面圖中的那樣。
基本過程為:
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 // 獲取執(zhí)行代碼... var result // 獲取執(zhí)行結(jié)果... var count // 獲取回調(diào)計數(shù)結(jié)果... el.innerHTML = ( renderSource(soure) + renderResult(result) + renderCount(count) ) document.body.appendChild(el) })
看到這里,已經(jīng)不耐煩的同學(xué)可以直接去 demo 頁面上扒代碼來看了,相較于我枯燥的描述,代碼可能看起來更簡單些。
(1)獲取執(zhí)行代碼
通過 demoFn.toString() 就可以了,不過需要額外去除函數(shù)定義的頭尾部分,只在頁面展示執(zhí)行代碼。
(2)獲取執(zhí)行結(jié)果
通過傳入的 print() 來收集執(zhí)行結(jié)果,也不復(fù)雜:
var output = [] var print = msg => output.push(msg)
然后將 print 函數(shù)傳入 demoFn 函數(shù),這樣代碼執(zhí)行后輸出的結(jié)果會被收集到 output 中,然后渲染到頁面就可以了。
(3)獲取回調(diào)計數(shù)結(jié)果
這個是比較復(fù)雜的部分,對應(yīng)的實現(xiàn)思路就是前面講的了。不過由于 Lazy.js 中每次方法調(diào)用返回的是新的序列對象,要多次生成代理,所以我將生成代理序列對象的代碼多帶帶抽出:
// 計數(shù)對象 var count = {map: 0, filter: 0} function proxySeq(seq) { var handler = { get(target, name) { // 特別處理 `map` 和 `filter` if (name === "map" || name === "filter") { // 返回一個可以實現(xiàn)計數(shù)的函數(shù) return fn => { // 這個 fn 是返回的函數(shù)被調(diào)用時傳入的回調(diào)函數(shù),把這個回調(diào) // 函數(shù)包裝一下再傳給原始序列對象的 map 或 filter 方法, // 從而實現(xiàn)調(diào)用計數(shù) var _fn = (v, i) => { count[name]++ // 計數(shù) return fn(v, i) // 調(diào)用回調(diào)函數(shù) } // 仍舊返回一個新的代理對象 return proxySeq(target[name](_fn)) } } else { return target[name] } } } return new Proxy(seq, handler) }
通過 proxySeq() 來實現(xiàn)一個 FakeLazy:
var _lazy = list => proxySeq(Lazy(list))
和前面的 print 函數(shù)一起作為參數(shù)來調(diào)用 demoFn 函數(shù)從而執(zhí)行代碼:
demoFn(_lazy, print)
執(zhí)行過程中可以收集執(zhí)行結(jié)果和回調(diào)函數(shù)執(zhí)行次數(shù),這是借助一個個代理對象來“劫持” map、filter 實現(xiàn)的。
渲染的過程的就是字符串拼接了,不再贅述。
小結(jié)代碼勝過萬語千言,感興趣的同學(xué)可以去讀一下 demo 頁面的源碼。
最后感嘆一下,Lazy.js 的實現(xiàn)還是蠻有意思的,之后我會結(jié)合 simply-lazy 分享下惰性求值的實現(xiàn)原理。對了,demo 頁面使用的其實是 simply-lazy,而非 Lazy.js。
其實無論是 simply-lazy 還是這里 demo 頁面的實現(xiàn),都有一些不足,例如 demo 頁面中其實沒有處理 take(),這樣后續(xù)如果再調(diào)用 map 或 filter,就無法計數(shù)。不過這些相對于我要介紹的東西而言,不是那么重要,咱們且得魚忘筌吧。^_^
最后的最后,感謝閱讀!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/91247.html
摘要:聯(lián)想到我在微信小程序上的開發(fā)體驗,真心覺得如果有熱更新機制的話,開發(fā)效率要高很多。熱更新示例下面通過例子來進一步解釋熱更新機制。 想必作為前端大佬的你,工作中應(yīng)該用過 webpack,并且對熱更新的特性也有了解。如果沒有,當(dāng)然也沒關(guān)系。 下面我要講的,是我對 Webpack 熱更新機制的一些認識和理解,不足之處,歡迎指正。 首先: 熱更新是啥? 熱更新,是指 Hot Module Re...
摘要:當(dāng)組件安裝和更新時,回調(diào)函數(shù)都會被調(diào)用。好在為我們提供了第二個參數(shù),如果第二個參數(shù)傳入一個數(shù)組,僅當(dāng)重新渲染時數(shù)組中的值發(fā)生改變時,中的回調(diào)函數(shù)才會執(zhí)行。 前言 首先歡迎大家關(guān)注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現(xiàn),能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關(guān)注呀!React 16.8中新增了Hooks特性,并且在React官方文檔中新增...
摘要:引用計數(shù)會記錄給定對象的引用個數(shù),并在引用個數(shù)為零時收集該對象。在對象群組內(nèi)部使用弱引用即不會在引用計數(shù)中被計數(shù)的引用有時能避免出現(xiàn)引用環(huán),因此弱引用可用于解決循環(huán)引用的問題。 參考 1.weakref – Garbage-collectable references to objects2.Python弱引用介紹 和許多其它的高級語言一樣,Python使用了垃圾回收器來自動銷毀那些不...
摘要:道阻且長啊前端面試總結(jié)前端面試筆試面試騰訊一面瀏覽器工作原理瀏覽器的主要組件包括用戶界面包括地址欄后退前進按鈕書簽?zāi)夸洖g覽器引擎用來查詢及操作渲染引擎的接口渲染引擎渲染界面和是基于兩種渲染引擎構(gòu)建的,使用自主研發(fā)的渲染引擎,和都使用網(wǎng)絡(luò)用來 道阻且長啊TAT(前端面試總結(jié)) 前端 面試 筆試 面試 騰訊一面 1.瀏覽器工作原理 瀏覽器的主要組件包括: 用戶界面- 包括地址欄、后退/前...
閱讀 1083·2023-04-25 15:42
閱讀 3683·2021-11-02 14:38
閱讀 2951·2021-09-30 09:48
閱讀 1530·2021-09-23 11:22
閱讀 3502·2021-09-06 15:02
閱讀 3252·2021-09-04 16:41
閱讀 676·2021-09-02 15:41
閱讀 2089·2021-08-26 14:13