有時候會在項目開發中頻繁地觸發一些事件,如 resize
、 scroll
、 keyup
、 keydown
等,或者諸如輸入框的實時搜索功能,咱們知道若是事件處理函數無限制調用,會大大加劇瀏覽器的工做量,有可能致使頁面卡頓影響體驗;後臺接口的頻繁調用,不只會影響客戶端體驗,還會大大增長服務器的負擔。而若是對這些調用函數增長一個限制,讓其減小調用頻率,豈不美哉?瀏覽器
針對這個問題,通常有兩個方案:
防抖 (Debounce)
節流 (Throttle)服務器
我對函數防抖的定義:當函數被連續調用時,該函數並不執行,只有當其所有中止調用超過必定時間後才執行1次。app
一個被常常提起的例子:dom
上電梯的時候,你們陸陸續續進來,電梯的門不會關上,只有當一段時間都沒有人上來,電梯纔會關門。
Talk is cheap,咱們直接 show code 吧。函數
先作基本的準備(篇幅緣由,HTML部分省略):工具
let container = document.getElementById('container'); // 事件處理函數 function handle(e) { onsole.log(Math.random()); } // 添加滾動事件 container.addEventListener('scroll', handle);
咱們發現,每滾動一下,控制檯就會打印出一行隨機數。this
咱們如今寫一個最基礎的防抖處理:code
function debounce(func, wait) { var timeout;//標記 return function() { clearTimeout(timeout); timeout = setTimeout(func, wait); } }
事件也作以下改寫:對象
container.addEventListener('scroll', debounce(handle, 1000));
如今試一下, 咱們會發現只有咱們中止滾動1秒鐘的時候,控制檯纔會打印出一行隨機數。接口
以上基礎版本會有兩個問題,請看以下代碼:
// 處理函數 function handle(e) { console.log(this); //輸出Window對象 console.log(e); //undefined }
沒錯,當咱們不使用防抖處理時,handle()
函數的this
指向調用此函數的container,而在外層使用防抖處理後,this的指向會變成Window。
其次,咱們也要獲取到事件對象event
。
因此咱們要對防抖函數作如下改寫:
function debounce(fn, wait) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(()=>{ fn.apply(this,arguments)//使用apply改變this指向 }, wait); } }
固然了,若是使用箭頭函數即可以省去外層聲明。
以上的狀況都是隻有當連續觸發中止後才執行,那若是咱們想讓事件第一次觸發就執行,後面的連續觸發都不執行,直到中止觸發一段時間才能夠再次觸發(好比防止頻繁點擊),該如何處理呢?
那麼能夠利用一樣的原理,稍做修改便可:
function debounce(fn, wait) { let timeout; return function(){ let arg = arguments; let that = this; clearTimeout(timeout); !timeout && fn.apply(that,arg) timeout = setTimeout(function(){ timeout = null; }, wait); } }
顧名思義,節流就是節約流量,將連續觸發的事件稀釋成預設評率。
好比每間隔1秒執行一次函數,不管這期間觸發多少次事件。
這有點像公交車, 不管在站點等車的人多很少,公交車只會按時來一班,不會來一我的就來一輛公交車。
function throttle(fn, wait) { let timeout; return function () { if (!timeout) { timeout = setTimeout(() => { timeout = null; fn.apply(this, arguments) }, wait) } } }
用滾動事件來描述節流,實際上是一個很是典型的場景,好比須要用滾動事件判斷是否加載更多等。
和防抖函數相似,以上的狀況是先等待後觸發,若是咱們想讓事件先觸發後等待,該如何處理呢?網上大部分文章都告訴你用時間戳的方式去實現,其實只要像防抖同樣稍做修改便可實現。
function throttle(fn, wait) { let timeout; return function () { if (!timeout) { fn.apply(this, arguments) timeout = setTimeout(() => { timeout = null; }, wait) } } }
這樣,咱們就會發現第一次觸發函數就會當即生效。
關於防抖與節流,lodash、underscore等工具庫都有完善的實現能夠直接用,本沒有必要造輪子。本文的目的僅僅是爲了將其主要思想和實現思路展示出來。更重要的,知道防抖和節流的本質後,就知道在什麼時候使用防抖或者節流,什麼時候先觸發或後觸發。不管需求如何改變,均可以靈活的運用。