JavaScript專題系列-防抖和節流
一般來說,這一段主要是講一些知識的大體概況,都不是那麼重要的,相當於文章的摘要。但是就是有不同尋常的,比如本文對於防抖以及節流的概念理解就很重要,非常重要。
1.1 出現原因
首先需要指出的是為什麼會出現這2種思想。
1.由於肉眼只能分辨出一定頻率的變化,也就是說一種變化1s內變化1000次和變成60次對人的感官是一樣的,同理,可以類推到js程式碼。在一定時間內,程式碼執行的次數不一定要非常多。達到一定頻率就足夠了。因為跑得越多,帶來的效果也是一樣。
2.客戶端的效能問題。眾所周知,就目前來說相容對應前端來說還是相當重要的,而主要的相容點在於低端機型,所以說我們有必要把js程式碼的執行次數控制在合理的範圍。既能節省瀏覽器CPU資源,又能讓頁面瀏覽更加順暢,不會因為js的執行而發生卡頓。
以上就是函式節流和函式防抖出現的主要原因。
1.2 概念理解
上面說了那麼多,只是為了說明為什麼會出現防抖和節流這2種實現,下面再來形象理解一下這兩種思想的不同之處,很多時候我都會把這兩種思想混淆,所以這次特意想了很好記住的辦法。
1. 函式節流 是指一定時間內js方法只跑一次。
節流節流就是 節省水流的意思 ,就想水龍頭在流水,我們可以手動讓水流(在一定時間內)小一點,但是他會一直在流。
當然還有一個形象的比喻,開源節流,就比如我們這個月(在一定時間內)我們少花一點錢,但是我們每天還是都需要花錢的。
2. 函式防抖 只有足夠的空閒時間,才執行程式碼一次。
比如生活中的坐公交,就是一定時間內,如果有人陸續刷卡上車,司機就不會開車。只有別人沒刷卡了,司機才開車。(其實只要記住了節流的思想就能通過排除法判斷節流和防抖了)
2.程式碼
2.1 防抖
上面的解釋都是為了形象生動地說明防抖和節流的思想以及區別,現在我們需要從程式碼層面來進一步探索防抖。
首先寫程式碼之前最重要的事情就是想在腦子裡面想這段程式碼需要實現什麼邏輯,下面就是防抖的程式碼邏輯思路。
你儘管觸發事件,但是我一定在事件觸發 n 秒後才執行,如果你在一個事件觸發的 n 秒內又觸發了這個事件,那我就以新的事件的時間為準,n 秒後才執行,總之,就是要等你觸發完事件 n 秒內不再觸發事件,我才執行!
好了,根據上面的思路我們可以很輕鬆地寫出第一版防抖的程式碼。
function debounce(func, waitTime) { var timeout; return function () { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(func, waitTime); } } document.querySelector('#app').onmousemove = debounce(fn, 1000); 複製程式碼
上面的一小段程式碼就是最原始的防抖程式碼。
可以看到上面這幾行程式碼就用到了閉包的知識,主要的目的就是為了在函式執行後保留timeout這個變數。
想讓一個函式執行完後,函式內的某個變數(timer)仍舊保留,就可以使用閉包把要儲存的變數在父作用域宣告,其他的語句放到子作用域裡,並且作為一個function返回。下面的很大例項程式碼都用到了閉包來解決保留變數的問題。
還有一點也許有小夥伴會有疑惑。為什麼這裡要返回一個函式呢。其實很好理解,我們可以來看下面的程式碼
var timeout; function debounce(func, waitTime) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(func, waitTime); } container.onmousemove = debounce(getUserAction, 1000); 複製程式碼
我手動刪掉了debounce函式裡面的return ,然後為了保留timeout,我把它放到了全域性變數,這幾行程式碼看起來和上面的很像,但是你可以直接跑一下這段程式碼,發現debounce只會執行一次!!!
哈哈哈,其實之所以在debounce函式裡面返回一個函式,那是因為onmousemove需要的是繫結的 函式 ,我們的測試程式碼執行一遍後只會返回undefined ,相當於
container.onmousemove = debounce(getUserAction, 1000); container.onmousemove = undefined; 複製程式碼
當然就沒有正確繫結事件了。如果從好理解的角度來寫,其實也是可以想下面這樣繫結的
var timeout; function debounce(func, waitTime) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(func, waitTime); } container.onmousemove = () => { debounce(getUserAction, 1000); } 複製程式碼
下面所有方法的道理都是和第一個函式一樣的。
但是這一版本的程式碼我們在fn中列印this以及event物件,發現有點不對。

可以從上圖中看到,fn中的this以及event物件,發現並不是希望的,所以我們需要手動把this以及event物件傳遞給fn函式。於是乎有了下面第二版的防抖函式。
function debounce(func, waitTime) { var timeout; return function () { var context = this, args = arguments; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(function () { func.apply(context, args) },waitTime); } } 複製程式碼
其實也就是用了apply函式把this以及event物件傳遞給fn函式。
2.2 節流
下面讓我們繼續來看一下節流思想的程式碼邏輯。
使用時間戳,當觸發事件的時候,我們取出當前的時間戳,然後減去之前的時間戳(最一開始值設為 0 ),如果大於設定的時間週期,就執行函式,然後更新時間戳為當前的時間戳,如果小於,就不執行。
ok,根據上面的邏輯,我們可以很輕鬆寫出第一版節流函式。
function throttle(func, waitTime) { var context, args, previous = 0; return function() { var now = + new Date(); context = this; args = arguments; if (now - previous > waitTime) { func.apply(context, args); previous = now; } } } 複製程式碼
或者我們其實還可以藉助定時器來實現節流。
當觸發事件的時候,我們設定一個定時器,再觸發事件的時候,如果定時器存在,就不執行,直到定時器執行,然後執行函式,清空定時器,這樣就可以設定下個定時器。
function throttle(func, waitTime) { var timeout, previous = 0; return function() { context = this; args = arguments; if (!timeout) { timeout = setTimeout(function(){ timeout = null; func.apply(context, args) }, waitTime) } } } 複製程式碼
3. 知識轉為技能
2018年,我最大的感悟就是儘量把所學的知識轉為技能(來自老姚 [ juejin.im/post/5c34ab… ](2018年收穫5條認知,條條振聾發聵 | 掘金年度徵文))
知識是可以學到的,但是技能只能習得。
上面兩部分我們都是在學防抖和節流出現的原因,對應的概念以及實現的思想邏輯,這些都是知識,現在就讓我們一起把學到的知識轉為技能,爭取成為自己專案的一部分吧。
對於像防抖和節流這種工具性質的函式,我們大可以把他們放在公共檔案裡面,然後在需要的地方直接呼叫就可以了。
防抖和節流最大的核心用處在於優化程式碼效能,可以用在很多地方,比如輸入框的驗證,圖片懶載入,各種頻繁觸發的DOM事件等等。
下面是我自己模擬寫了一個百度搜索的按鈕精靈,圖一是沒有用防抖搜尋 我是 這個關鍵詞發現發起了N多次請求,然後改了一行程式碼加入了防抖,請求的情況就變成了圖二。效果顯而易見。
this.debounce(this.getData, 1000)(); 複製程式碼

