1、什麼是節流和去抖?
節流。就是扭緊水龍頭讓水少流一點,但不是不讓水流了。想像一下在現實生活中有時候我們需要接一桶水,接水的同時不想一直站在那等著,可能要離開一會兒去乾一點別的事請,讓水差不多流滿一桶水的時候再回來,這時候,不能把水龍頭開的太大,不然還沒回來水就已經滿了,浪費了很多水,這時候就需要節流,讓自己回來的時候水差不多滿了。那在JS裡有沒有這種情況呢,典型的場景是圖片懶加載監聽頁面的scoll事件,或者監聽滑鼠的mousemove事件,這些事件對應的處理方法相當於水,由於scroll和mousemove在滑鼠移動的時候會被瀏覽器頻繁的觸發,會導致對應的事件也會被頻繁的觸發(水流的太快了),這樣就會造成很大的瀏覽器資源開銷,而且好多中間的處理是不必要的,這樣就會造成瀏覽器卡頓的現象,這時候就需要節流,如何節流呢?我們無法做到讓瀏覽器不會觸發對應的事件,但可以做到讓處理事件的方法執行頻率減少,進而減少對應的處理開銷。
去抖。最早接觸這個詞應該是在高中物理裡面學到的,有時候開關在在真正閉合之前可能會發生一些抖動現象,如果抖動的明顯的話,對應的小燈泡可能會閃爍,把燈泡閃壞了不重要,萬一把眼睛再給閃壞了可就麻煩了,這時候就有去抖電路的出現。而在我們的頁面裡,也有這種情況,假設我們的一個輸入框,輸入內容的同時可能會去後台查詢對應的聯想詞,如果用戶輸入的同時,頻繁的觸發input事件,然後頻繁的向後抬發送請,那麼直到用戶輸入完成時,之前的請求都應該是多餘的,假設網絡慢一點,後台返回的數據比較慢,那麼顯示的聯想詞可能會出現頻繁的變換,直到最後的一個請求返回。這個時候就可以在一定時間內監聽是否再次輸入,如果沒有再次輸入則認為本次輸入完成,發送請求,否則就是判定用戶仍在輸入,不發送請求。
去抖動和節流是不同的,因為節流雖然中間的處理函數被限制了,但是只是減少了頻率,而去抖則把中間的處理函數全部過濾掉了,只執行規判定時間內的最後一個事件。
2、JS實作。
節流:
/** 实现思路: ** 参数需要一个执行的频率,和一个对应的处理函数, ** 内部需要一个lastTime 变量记录上一次执行的时间 **/ function throttle (func, wait) { let lastTime = null // 为了避免每次调用lastTime都被清空,利用js的闭包返回一个function确保不生命全局变量也可以 return function () { let now = new Date() // 如果上次执行的时间和这次触发的时间大于一个执行周期,则执行 if (now - lastTime - wait > 0) { func() lastTime = now } } }
#再看如何呼叫:
// 由于闭包的存在,调用会不一样 let throttleRun = throttle(() => { console.log(123) }, 400) window.addEventListener('scroll', throttleRun)
這時候f瘋狂的滾動頁面,會發現會400ms打印一個123,而沒有節流的話會不斷地打印, 你可以改變wait參數去感受下不同。
但是到這裡,我們的節流方法是不完善的,因為我們的方法沒有獲取事件發生時的this對象,而且由於我們的方法簡單粗暴的通過判斷這次觸發的時間和上次執行時間的間隔來決定是否執行回調,這樣就會造成最後一次觸發無法執行,或者用戶出發的間隔確實很短,也無法執行,造成了誤殺,所以需要對方法進行完善。
function throttle (func, wait) { let lastTime = null let timeout return function () { let context = this let now = new Date() // 如果上次执行的时间和这次触发的时间大于一个执行周期,则执行 if (now - lastTime - wait > 0) { // 如果之前有了定时任务则清除 if (timeout) { clearTimeout(timeout) timeout = null } func.apply(context, arguments) lastTime = now } else if (!timeout) { timeout = setTimeout(() => { // 改变执行上下文环境 func.apply(context, arguments) }, wait) } } }
這樣我們的方法就相對完善了,呼叫方法和之前都相同。
去抖:
去抖的方法,和節流思路一致,但是只有在抖動被判定結束後,方法才會被執行。
debounce (func, wait) { let lastTime = null let timeout return function () { let context = this let now = new Date() // 判定不是一次抖动 if (now - lastTime - wait > 0) { setTimeout(() => { func.apply(context, arguments) }, wait) } else { if (timeout) { clearTimeout(timeout) timeout = null } timeout = setTimeout(() => { func.apply(context, arguments) }, wait) } // 注意这里lastTime是上次的触发时间 lastTime = now } }
這時候按照之前同樣的方式調用,會發現無論怎麼瘋狂的滾動窗口,只有停止滾動時,才會執行對應的事件。
去抖和節流已經有很多成熟的js進行了實現,其大致思路基本上是這樣的。
我們再給大家分享一下網友的實作方法的程式碼:
方法一
1.這種實作方式的想法很好理解:設定一間隔時間,例如50毫秒,以此時間為基準設定定時器,當第一次觸發事件到第二次觸發事件間隔小於50毫秒時,清除這個定時器,並設定新的定時器,以此類推,直到有一次事件觸發後50毫秒內沒有重複觸發。程式碼如下:
function debounce(method){ clearTimeout(method.timer); method.timer=setTimeout(function(){ method(); },50); }
這種設計方式有一個問題:本來應該多次觸發的事件,可能最終只會發生一次。具體來說,一個循序漸進的滾動事件,如果使用者滾動太快速,或者程式設定的函數節流間隔時間太長,那麼最終滾動事件會呈現為一個很突然的跳躍事件,中間過程都被節流截掉了。這個例子舉的有點誇張了,不過使用這種方式進行節流最終是會明顯感受到程式比不節流的時候“更突兀”,這對於用戶體驗是很差的。有一種彌補這種缺陷的設計想法。
方法二
2.第二种实现方式的思路与第一种稍有差别:设置一个间隔时间,比如50毫秒,以此时间为基准稳定分隔事件触发情况,也就是说100毫秒内连续触发多次事件,也只会按照50毫秒一次稳定分隔执行。代码如下:
var oldTime=new Date().getTime(); var delay=50; function throttle1(method){ var curTime=new Date().getTime(); if(curTime-oldTime>=delay){ oldTime=curTime; method(); } }
相比于第一种方法,第二种方法也许会比第一种方法执行更多次(有时候意味着更多次请求后台,即更多的流量),但是却很好的解决了第一种方法清除中间过程的缺陷。因此在具体场景应根据情况择优决定使用哪种方法。
对于方法二,我们再提供另一种同样功能的写法:
var timer=undefined,delay=50; function throttle2(method){ if(timer){ return ; } method(); timer=setTimeout(function(){ timer=undefined; },delay); }
以上内容就是JS函数去抖和节流详解,希望能帮助到大家。
相关推荐:
以上是詳細分析JS函數去抖動和節流的詳細內容。更多資訊請關注PHP中文網其他相關文章!