作為一名前端開發者,我們經常會處理各種事件,比如常見的click、scroll、 resize等等。仔細一想,會發現像scroll、scroll、onchange這類事件會頻繁觸發,如果我們在回調中計算元素位置、做一些跟DOM相關的操作,引起瀏覽器迴流和重繪,頻繁觸發回調,很可能會造成瀏覽器掉幀,甚至會使瀏覽器崩潰,影響用戶體驗。針對這種現象,目前有兩種常用的解決方案:防抖和節流。
var debounce = function(fn, delayTime) { var timeId; return function () { var context = this, args = arguments; timeId && clearTimeout(timeout); timeId = setTimeout(function { fn.apply(context, args); }, delayTime) } }
類比到生活中的水龍頭,擰緊水龍頭到某種程度會發現,每隔一段時間,就會有水滴流出。
var throttle = (fn, delayTime) => { var _start = Date.now(); return function () { var _now = Date.now(), context = this, args = arguments; if(_now - _start >= delayTime) { fn.apply(context, args); _start = Date.now(); } } }
var throttle = function (fn, delayTime) { var flag; return function () { var context = this, args = arguments; if(!flag) { flag = setTimeout(function () { fn.apply(context, args); flag = false; }, delayTime); } } }
1、使用時間戳方式,頁面載入的時候就會開始計時,如果頁面載入時間大於我們設定的delayTime,第一次觸發事件回調的時候便會立即fn,並不會延遲。如果最後一次觸發回調與前一次觸發回調的時間差小於delayTime,則最後一次觸發事件並不會執行fn;
var throttle = function (fn, delayTime) { var flag, _start = Date.now(); return function () { var context = this, args = arguments, _now = Date.now(), remainTime = delayTime - (_now - _start); if(remainTime <= 0) { fn.apply(this, args); } else { setTimeout(function () { fn.apply(this, args); }, remainTime) } } }
與setTimeout相比,requestAnimationFrame的時間間隔是有系統來決定,保證屏幕刷新一次,回調函數只會執行一次,比如屏幕的刷新頻率是60HZ,即間隔1000ms/60會執行一次回調。
var throttle = function(fn, delayTime) { var flag; return function() { if(!flag) { requestAnimationFrame(function() { fn(); flag = false; }); flag = true; } }
上面的示例代碼比較簡單,只是說明了基本的思路。目前已經有工具庫實現了這些功能,比如underscore,考慮的情況也會比較多,大家可以去查看源碼,學習作者的思路,加深理解。
_.debounce = function(func, wait, immediate) { var timeout, result;
var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); };
var debounced = restArguments(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); }
return result; });
debounced.cancel = function() { clearTimeout(timeout); timeout = null; };
return debounced; };
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {};
var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; };
var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; };
throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; };
return throttled; };
推薦閱讀: