解決當函數在某些場景下被無限制的頻繁調用,會增長瀏覽器的負擔,會形成卡頓的現象瀏覽器
鼠標滾動、鍵盤輸入操做、窗口resize等等閉包
防抖:事件持續觸發時,若是間隔時間小於預設的時間,不會執行函數,同時以最後觸發事件的時間爲準從新計時。總之,必定要在事件觸發完的預設時間內沒有觸發過,纔會執行函數app
手寫+逐步完善函數
function _debounce(func, wait){ // 定義一個計時器 var timer return function(){ // 拿到當前的執行上下文 var context = this clearTimeout(timer) timer = setTimeout(function(){ // 由於setTimeout回調方法中this永遠是window,因此~ func.apply(context) },wait) } }
以上實現方式有一個不足,就是當事件處理函數傳入參數(event對象)時,沒有作到獲取,修改一下:優化
function _debounce(func, wait){ var timer return function(){ var context = this // 經過arguments拿到調用時所傳入的參數 var arg = arguments clearTimeout(timer) timer = setTimeout(function(){ // 執行時傳入參數 func.apply(context, arg) },wait) } }
此時還有一個問題,最開始函數必需要在事件初次觸發後,等待wait以後纔會執行,咱們想要用一次當即執行來兜底初次觸發:this
function _debounce(func, wait, immediate){ var timer return function(){ var context = this var arg = arguments if(timer) clearTimeout(timer) if(immediate){ /** timer爲true,callNow爲false期間 * 都表明不是初次觸發 * timer初始布爾值爲false * 初次執行以後timer在wait時間內一直爲true **/ var callNow = !timer // 這一句在執行完以前 timer都爲true timer = setTimeout(function(){ timer = null },wait) if(callNow) func.apply(context,arg) }else{ timer = setTimeout(function(){ func.apply(context, arg) },wait) } } } // 中心思想就是有一個標識,用來標記當前是否爲初次觸發,用以判斷是否須要執行,而在執行後wait時間後將標識變爲初始狀態
**特別版本:防抖函數有可能會形成一直不觸發的現象code
function throttle(fn, delay){ /*last爲上一次觸發回調的時間, timer是定時器*/ var last = 0, timer = null return function () { let context = this let args = arguments let now = +new Date() /*判斷上次觸發的時間和本次觸發的時間差是否小於時間間隔的閾值*/ if (now - last < delay) { /* 若是時間間隔小於咱們設定的時間間隔閾值, * 則爲本次觸發操做設立一個新的定時器 * 其實這裏就是本來正常的防抖邏輯 */ clearTimeout(timer) timer = setTimeout(function () { last = now fn.apply(context, args) }, delay) } else { /* 若是時間間隔超出了咱們設定的時間間隔閾值,那就不等了,不管如何要反饋給用戶一次響應*/ last = now fn.apply(context, args) } } }
還能夠有一些優化,好比有返回值時等等,暫時到這裏 最好能記牢對象
節流:事件持續觸發期間,每間隔預設的時間纔會執行一次事件
手寫+逐步完善(定時器 or 時間戳)
一、時間戳版本rem
function _throttle(func, wait){ var timer = 0 return function(){ var arg = arguments var now = new Date() if(now - timer >= wait){ func.apply(this,arg) timer = now } } } /** 這裏懵逼了,理解一下應該是將throttle執行後的結果, * 也就是閉包函數賦值給了container的mousemove * 以後每次mousemove執行的都是閉包函數 **/ container.onmousemove = throttle(xxx,1000)
二、定時器版本
function _throttle(func,wait){ var timer return function(){ var arg = arguments if(!timer){ timer = setTimeout(function(){ timer = null func.apply(this,arg) },wait) } } }
以上兩種實現方式,時間戳版本會當即執行,但不會有最後一一次執行;定時器版本不會當即執行,但會有最後一次操做執行兜底
最好是將這兩種狀況合二爲一,更加優秀
三、取長補短版本:
function throttle(func, wait) { var timeout, context, args, result; var previous = 0; var later = function() { previous = +new Date(); timeout = null; func.apply(context, args) }; var throttled = function() { var now = +new Date(); //下次觸發 func 剩餘的時間 var remaining = wait - (now - previous); context = this; args = arguments; // 若是沒有剩餘的時間了或者你改了系統時間 if (remaining <= 0 || remaining > wait) { // 對於時間戳計算,要執行的時候若是還有計時器搗亂 要清掉 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); } else if (!timeout) { // 若是時間戳尚未到執行,定時器到了(沒有定時器表明要執行了),那就在剩餘時間後執行,兜最後一次 timeout = setTimeout(later, remaining); } }; return throttled; } // 這個難 暫時先理解吧