本篇課題,或許早已經是爛大街的解讀文章。不過春招系列面試下來,很多夥伴們仍是似懂非懂地栽倒在(~面試官~)深意的笑容之下,權當溫故知新。javascript
JavaScript的執行過程,是基於棧來進行的。複雜的程序代碼被封裝到函數中,程序執行時,函數不斷被推入執行棧中。因此 "執行棧" 也稱 "函數執行棧"。前端
函數中封裝的代碼塊,通常都有相對複雜的邏輯處理(計算/判斷),例如函數中可能會涉及到 DOM
的渲染更新,複雜的計算與驗證, Ajax
數據請求等等。java
前端頁面的操做權,大部分都是屬於瀏覽端的客戶爸爸們(單身三十年的手速,惹不起惹不起!!!)。若是函數被頻繁調用,形成的性能開銷絕對不僅一點點。面試
DOM
頻繁重繪的卡頓讓客戶爸爸們想把你揪出來一頓大招。。。既要提高用戶體驗,又要減小後端服務開銷,可見咱們大前端的使命不僅一頁PPT。說好前因,接着就是後果了。既然有優化的需求,必然就要有相應的解決方案。隆重請出主角: 「防抖」 與 「節流」。後端
在事件被觸發 n 秒後再執行回調函數,若是在這 n 秒內又被觸發,則從新計時延遲時間。
生活化理解:英雄的技能條,技能條讀完才能使用技能(R大招60s)性能優化
防抖的實現方式分兩種 「當即執行」 和 「非當即執行」,區別在於第一次觸發時,是否當即執行回調函數。app
」非當即執行防抖「 指事件觸發後,回調函數不會當即執行,會在延遲時間 n 秒後執行,若是 n 秒內被調用屢次,則從新計時延遲時間
// e.g. 防抖 - 非當即執行 function debounce(func, delay) { var timeout; return function() { var context = this; var args = arguments; // && 短路運算 == if(timeout) else {...} timeout && clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args); }, delay); } } // 調用 var printUserName = debounce(function(){ console.log(this.value); }, 800); document.getElementById('username') .addEventListener('keyup', printUserName);
「當即執行防抖」 指事件觸發後,回調函數會當即執行,以後要想觸發執行回調函數,需等待 n 秒延遲
// e.g. 防抖 - 當即執行 function debounce(func, delay) { var timeout; return function() { var context = this; var args = arguments; callNow = !timeout; timeout = setTimeout(function() { timeout = null; }, delay); callNow && func.apply(context, args); } }
函數防抖原理:經過維護一個定時器,其延遲計時以最後一次觸發爲計時起點,到達延遲時間後纔會觸發函數執行。函數
規定在一個單位時間內,只能觸發一次函數。若是這個單位時間內觸發屢次函數,只有一次生效(間隔執行)
生活化理解:工具
函數節流實現的方式有 「時間戳」 和 「定時器」 兩種。post
// e.g. 節流 - 時間戳 function throttle(func, delay) { var lastTime = 0; return function() { var context = this; var args = arguments; var nowTime = +new Date(); if (nowTime > lastTime + delay) { func.apply(context, args) lastTime = nowTime; } } }
「時間戳」 的方式,函數在時間段開始時執行。
缺點:假定函數間隔1s執行,若是最後一次中止觸發,卡在4.2s,則不會再執行。
// e.g. 節流 - 定時器 function throttle(func, delay) { var timeout; return function() { var context = this; var args = arguments; if (!timeout) { setTimeout(function(){ func.apply(context, args); timeout = null; }, delay) } } }
「定時器」 的方式,函數在時間段結束時執行。可理解爲函數並不會當即執行,而是等待延遲計時完成才執行。(因爲定時器延時,最後一次觸發後,可能會再執行一次回調函數)
// e.g. 節流 - 時間戳 + 定時器 function throttle(func, delay) { let lastTime, timeout; return function() { let context = this; let args = arguments; let nowTime = +new Date(); if (lastTime && nowTime < lastTime + delay) { timeout && clearTimeout(timeout); timeout = setTimeout(function(){ lastTime = nowTime; func.apply(context, args); }, delay); } else { lastTime = nowTime; func.apply(context, args); } } }
合併優化的原理:「時間戳」方式讓函數在時間段開始時執行(第一次觸發當即執行),「定時器」方式讓函數在最後一次事件觸發後(如4.2s)也能觸發。
函數節流原理:必定時間內只觸發一次,間隔執行。經過判斷是否到達指定觸發時間,間隔時間固定。
相同:都是防止某一時間段內,函數被頻繁調用執行,經過時間頻率控制,減小回調函數執行次數,來實現相關性能優化。
區別:「防抖」是某一時間內只執行一次,最後一次觸發後過段時間執行,而「節流」則是間隔時間執行,間隔時間固定。
scroll
resize
mousemove
拖拽應用場景還有不少,具體場景需具體分析。只要涉及高頻的函數調用,均可參考函數防抖節流的優化方案。
鼓起勇氣寫在結尾:以上代碼都不是 「完美」 的 「防抖 / 節流」 實現代碼!!!僅就實現方式和基本原理,淺談分解一二。
實際代碼開發中,通常會引入lodash
相對 「靠譜」 的第三方庫,幫咱們去實現防抖節流的工具函數。有興趣的夥伴們可閱讀 lodash
相關源碼,加深印象理解可再讀如下參考文章。
參考文章