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