摘要:結(jié)束語在這里,我們雖然僅僅涉及了一些高階函數(shù)應(yīng)用的皮毛,但這兩個(gè)技巧,實(shí)是項(xiàng)目開發(fā)當(dāng)中克敵制勝,提高性能的實(shí)戰(zhàn)利器。
通過函數(shù)節(jié)流與函數(shù)分時(shí)提升應(yīng)用性能在例如表單自動(dòng)補(bǔ)全,數(shù)據(jù)埋點(diǎn),文章內(nèi)容自動(dòng)保存,視口監(jiān)聽,拖拽,列表渲染等高頻操作時(shí),如果同時(shí)有其它UI行為占據(jù)線程,瀏覽器端時(shí)常會(huì)出現(xiàn)卡頓現(xiàn)象,服務(wù)器端也面臨著較大壓力。這時(shí),函數(shù)節(jié)流,與函數(shù)分時(shí)將成為我們的一大輔助。
一、函數(shù)節(jié)流
看一則自動(dòng)補(bǔ)全的例子
//自動(dòng)補(bǔ)全 const input = document.querySelector("#autocompleteSearch"), completeArr = []; //需要被渲染的補(bǔ)全提示數(shù)據(jù) input.addEventListener("keydown", e => { const value = e.currentTarget.value, xhr = new XMLHttpRequest(); xhr.addEventListener("load", ()=> { //請(qǐng)求完成后,將數(shù)據(jù)裝載到數(shù)組中 xhr.status === 200 && completeArr.push(xhr.responseText); }); xhr.open("GET", "http://api.com"); xhr.send(value); });
在這里,我沒有提供具體的UI層的操作,只提供了觸發(fā)事件時(shí)的handler,實(shí)際的開發(fā)中可能還需要涉及要補(bǔ)全數(shù)據(jù)數(shù)組的渲染邏輯以及排序和清除邏輯,但這并不妨礙我們理解本問題的過程。
可以看到的是,為了實(shí)時(shí)更新補(bǔ)全數(shù)據(jù),每次當(dāng)用戶按下按鍵時(shí),我們都要向服務(wù)器去發(fā)起一次請(qǐng)求。如果產(chǎn)品的用戶基數(shù)很大,并發(fā)一高,那就實(shí)在是有些坑后端隊(duì)友了。
回想需求,我們要根據(jù)用戶輸入的關(guān)鍵字像服務(wù)器索取補(bǔ)全的字段,反饋給用戶快速選擇。
實(shí)際上,在用戶輸入表單的過程中,可能按下很多次按鍵才會(huì)打出一個(gè)字,或者是打出了很多個(gè)字后,才能檢索出真整的數(shù)據(jù)。
基于這個(gè)角度來換一下思路,如何限制請(qǐng)求的發(fā)送呢?
判斷value的長(zhǎng)度,輸入兩個(gè)三個(gè)字以上,再向服務(wù)器發(fā)起請(qǐng)求
將事件的handler觸發(fā)頻率降低
第一種思路,不失為是一種可行的方案,但是很難復(fù)用,而且用戶真實(shí)想要搜入的字?jǐn)?shù)并不確定。
第二種思路,既能限制頻率,減少請(qǐng)求,還能近實(shí)時(shí)向用戶反饋,無視用戶輸入的字符串長(zhǎng)度,還可以實(shí)現(xiàn)高復(fù)用。
下面提供實(shí)現(xiàn)的方式,首先,實(shí)現(xiàn)函數(shù)節(jié)流:
const throttle = (fn, time = 1000)=> { let triggered = true, // 首次觸發(fā)狀態(tài)的標(biāo)識(shí) timer; // 定時(shí)器觸發(fā)標(biāo)識(shí) return function () { if (triggered) { // 首次觸發(fā) 回調(diào)直接執(zhí)行 fn.apply(this, arguments); //執(zhí)行后 使首次觸發(fā)標(biāo)識(shí)為假 return triggered = false; } if (timer) { // 定時(shí)器標(biāo)識(shí) 如果為真 代表著之前的分流限制范圍 尚未結(jié)束 return false; } timer = setInterval(()=> { //如果定時(shí)器標(biāo)識(shí)不為真 則為定時(shí)器賦上引用 clearInterval(timer); // 取反定時(shí)器標(biāo)識(shí) timer = !timer; // 執(zhí)行回調(diào) fn.apply(self, arguments); }, time) } };
上述代碼,利用了閉包與高階函數(shù),限制了函數(shù)的觸發(fā),關(guān)鍵點(diǎn)在于首次觸發(fā)與之前的節(jié)流是否結(jié)束的判斷。
改造一下上面的自動(dòng)補(bǔ)全代碼。
const input = document.querySelector("#autocompleteSearch"), completeArr = [], keydownHandler = throttle(e => { const value = e.currentTarget.value, xhr = new XMLHttpRequest(); xhr.addEventListener("load",()=> { //請(qǐng)求完成后,將數(shù)據(jù)裝載到數(shù)組中 xhr.status === 200 && completeArr.push(xhr.responseText); }); xhr.open("GET", "http://api.com"); xhr.send(value); }); //需要被渲染的補(bǔ)全提示數(shù)據(jù) input.addEventListener("keydown",keydownHandler); function throttle(fn, time = 1000) { let triggered = true, // 首次觸發(fā)狀態(tài)的標(biāo)識(shí) timer; // 定時(shí)器觸發(fā)標(biāo)識(shí) return function () { if (triggered) { // 首次觸發(fā) 回調(diào)直接執(zhí)行 fn.apply(this, arguments); //執(zhí)行后 使首次觸發(fā)標(biāo)識(shí)為假 return triggered = false; } if (timer) { // 定時(shí)器標(biāo)識(shí) 如果為真 代表著之前的分流限制范圍 尚未結(jié)束 return false; } timer = setInterval(()=> { //如果定時(shí)器標(biāo)識(shí)不為真 則為定時(shí)器賦上引用 clearInterval(timer); // 取反定時(shí)器標(biāo)識(shí) timer = !timer; // 執(zhí)行回調(diào) fn.apply(self, arguments); }, time) } }
如此,實(shí)現(xiàn)了keydown事件觸發(fā)的頻率,當(dāng)然,一些其他高頻的事件回調(diào)依舊適合,我們可以根據(jù)具體的業(yè)務(wù)場(chǎng)景,來傳入合理的time值,達(dá)到節(jié)流,既減輕了服務(wù)器端的壓力,又提升了性能,例如上面的自動(dòng)補(bǔ)全,1秒的延遲,用戶幾乎感受不到,何樂而不為呢?
二、分時(shí)函數(shù)
上面那種隱藏在用戶操作背后,節(jié)流函數(shù)是一個(gè)很好的解決方案。同時(shí),我們可能會(huì)面臨另外一種場(chǎng)景,即是一次性渲染。
比如說,我們有這樣的需求,后臺(tái)給了我們2000行記錄的數(shù)據(jù),要一次性用列表全部渲染出來。
2000行數(shù)據(jù)可不是一個(gè)小數(shù)目,如果里面內(nèi)嵌了很多子節(jié)點(diǎn)邏輯,那么很有可能我們也許要渲染上萬個(gè)節(jié)點(diǎn),眾所周知,DOM可是瀏覽器環(huán)境性能的最大損耗者。為了提升用戶體驗(yàn)與性能,通常情況下,我會(huì)使用兩種操作。
使用DOM的fragment,避免每次節(jié)點(diǎn)生成時(shí)的反復(fù)插入,可以在合理的時(shí)機(jī)向相應(yīng)的節(jié)點(diǎn)插入,這方面的資料很多,可以自行查閱。
使用函數(shù)分時(shí) 來分批處理渲染邏輯
先看如何在不分時(shí)的情況下操作節(jié)點(diǎn):
const list = document.querySelector("#ul"), virtualList = document.createDocumentFragment(), // 虛擬dom容器 listArr = [ {text: "hello react!"} // 假設(shè)這里有2000條記錄 ]; for (let i of listArr) { // 使用for of 遍歷數(shù)據(jù) const li = document.createElement("li"); li.textContent = i; // 插入虛擬容器中 virtualList.appendChild(li); } // 把載滿節(jié)點(diǎn)的虛擬容器 插入到真實(shí)的列表元素中 list.appendChild(virtualList);
再來看分函數(shù)分時(shí)的實(shí)現(xiàn):
function chunkFunc({fn, arr, count = arr.length, time = 200, sCb, aCb}) { /* * @params * fn : 需要被分時(shí)的處理邏輯 * arr : 全部的業(yè)務(wù)數(shù)據(jù) * count: 每次分時(shí)的具體數(shù)量 * 假設(shè)總共2000條數(shù)據(jù) * 我們可以設(shè)定 * 每次分成200條執(zhí)行 * 默認(rèn)為業(yè)務(wù)數(shù)據(jù)的長(zhǎng)度 * time : 分時(shí)的時(shí)間間隔 默認(rèn)200 毫秒 * sCb : singleCallback 每次分時(shí)遍歷結(jié)束時(shí)執(zhí)行的回調(diào) * aAb : allCallback 全部遍歷結(jié)束時(shí)需要做的回調(diào) * */ let timer, // 用以分時(shí)的定時(shí)器標(biāo)識(shí) start; // 遍歷處理邏輯 start = () => { for (let i = 0; i < count; i++) { //如果count給了值 我們循環(huán)count次 每次循環(huán)都從業(yè)務(wù)數(shù)據(jù)里取值 然后執(zhí)行處理邏輯 fn(arr.shift()); } //分時(shí)遍歷結(jié)束 如果有回調(diào) 執(zhí)行回調(diào) sCb && sCb(); }; return () => { // 默認(rèn)每200毫秒執(zhí)行一次 timer = setInterval(function () { // 如果原始數(shù)據(jù)被取空了 則停止執(zhí)行 if (arr.length === 0) { aCb && aCb(); return clearInterval(timer) } // 不然 執(zhí)行遍歷邏輯 start(); }, time); } }
實(shí)現(xiàn)方式很簡(jiǎn)單,即根據(jù)用戶給定的分時(shí)單位與時(shí)間,利用定時(shí)器重新包裝用戶處理邏輯,這里我們需要將渲染邏輯稍微改動(dòng),抽離出遍歷邏輯,添加遍歷結(jié)束回調(diào)方法(可選)。
重構(gòu)代碼如下:
const list = document.querySelector("#ul"), listArr = [ {text: "hello react!"} // 假設(shè)這里有2000條記錄 ]; let virtualDOM = document.createDocumentFragment(); chunkFunc({ fn(data) { // 生成節(jié)點(diǎn)邏輯 const li = document.createElement("li"); li.textContent = data.text; virtualDOM.appendChild(li); }, sCb() { // 分時(shí)遍歷結(jié)束 將虛擬節(jié)點(diǎn) 插入LIST list.appendChild(virtualDOM); // 重置虛擬節(jié)點(diǎn) 避免重復(fù)生成節(jié)點(diǎn) virtualDOM = document.createDocumentFragment(); }, aCb() { // 最終結(jié)束后 解除引用 virtualDOM = null; }, arr : listArr, count: 8, time : 300, })();
通過抽離了插入、生成節(jié)點(diǎn)的邏輯、給出不同階段的回調(diào),我們成功的將本來需要一次性生成的節(jié)點(diǎn),分批生成,提高了性能和用戶體驗(yàn)。
結(jié)束語
在這里,我們雖然僅僅涉及了一些高階函數(shù)應(yīng)用的皮毛,但這兩個(gè)技巧,實(shí)是項(xiàng)目開發(fā)當(dāng)中克敵制勝,提高性能的實(shí)戰(zhàn)利器。根據(jù)不同的業(yè)務(wù)場(chǎng)景伸縮,我們可以衍生出不同的方法。如果能結(jié)合單例模式,代理模式等常用的設(shè)計(jì)模式,將會(huì)有更為廣擴(kuò)的發(fā)揮。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91993.html
摘要:設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記最近利用碎片時(shí)間在上面閱讀設(shè)計(jì)模式與開發(fā)實(shí)踐讀書這本書,剛開始閱讀前兩章內(nèi)容,和大家分享下我覺得可以在項(xiàng)目中用的上的一些筆記。事件綁定暫時(shí)這么多,以后會(huì)不定期更新一些關(guān)于我讀這本書的筆記內(nèi)容 JavaScript 設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記 最近利用碎片時(shí)間在 Kindle 上面閱讀《JavaScript 設(shè)計(jì)模式與開發(fā)實(shí)踐讀書》這本書,剛開始閱讀前兩章內(nèi)容,...
摘要:原理連續(xù)觸發(fā)事件,但是事件函數(shù)只在在規(guī)定的周期之內(nèi)只執(zhí)行一次。代碼實(shí)現(xiàn)使用在使用節(jié)流函數(shù)后,我們?cè)跁和]斎氲暮缶蜁?huì)輸入輸入框內(nèi)的值,暫停時(shí)間小于,則不會(huì)輸出,將重新計(jì)算函數(shù)執(zhí)行時(shí)間。使用分時(shí)函數(shù)這樣在調(diào)用分時(shí)函數(shù)后每隔創(chuàng)建個(gè)節(jié)點(diǎn)。 一、節(jié)流函數(shù) 1. 使用場(chǎng)景 DOM.onclick()事件,我們給一個(gè)DOM節(jié)點(diǎn)綁定了點(diǎn)擊事件,當(dāng)點(diǎn)擊該元素時(shí)觸發(fā)事件函數(shù)的執(zhí)行,但是當(dāng)我們頻繁點(diǎn)擊該元素...
摘要:上傳進(jìn)度下面通過高階函數(shù)的方式我們來實(shí)現(xiàn)函數(shù)節(jié)流節(jié)流函數(shù)計(jì)時(shí)器是否是第一次調(diào)用首次調(diào)用直接放行存在計(jì)時(shí)器就攔截設(shè)置使用節(jié)流分時(shí)函數(shù)節(jié)流函數(shù)為我們提供了一種限制函數(shù)被頻繁調(diào)用的解決方案。 高階函數(shù)是指至少滿足下列條件之一的函數(shù) 1:函數(shù)可以作為參數(shù)被傳遞 2:函數(shù)可以作為返回值輸出 JavaScript語言中的函數(shù)顯然的是滿足了高階函數(shù)的條件,下面我們一起來探尋JavaScript種高階...
摘要:一前言本文適合有一定開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問題,理解這些問題有助于我們快速提升對(duì)這門語言的理解和應(yīng)用能力。 一:前言 本文適合有一定JS開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問題,理解這些問題有助于我們快速提升對(duì)JS這門語言的理解和應(yīng)用能力。文章只講述具體問題中的關(guān)鍵問題,不涵蓋全面的知識(shí)點(diǎn)。如想了解具體的知識(shí),可以參考筆者博客的相關(guān)文章...
閱讀 3128·2021-11-10 11:36
閱讀 3322·2021-10-13 09:40
閱讀 6147·2021-09-26 09:46
閱讀 675·2019-08-30 15:55
閱讀 1419·2019-08-30 15:53
閱讀 1589·2019-08-29 13:55
閱讀 3005·2019-08-29 12:46
閱讀 3218·2019-08-29 12:34