曾經面試時候被問到過這個,年少的我一臉無知。。。前端
後來工做中遇到了一個場景:輸入名稱的同時去服務器校驗名稱是否重複,但發現以前的代碼居然都沒作限制,輸入一次發一次請求。簡直忍不了,就在項目的utils里加上了防抖函數。
正好作一個總結,加深印象。面試
函數防抖和節流,都是控制事件觸發頻率的方法。應用場景有不少,輸入框持續輸入,將輸入內容遠程校驗、屢次觸發點擊事件、onScroll等等。
爲了說明問題,假設一個場景:鼠標滑過一個div,觸發onmousemove事件,它內部的文字會顯示當前鼠標的座標。服務器
<style> #box { width: 1000px; height: 500px; background: #ccc; font-size: 40px; text-align: center; line-height: 500px; } </style> <div id="box"></div> <script> const box = document.getElementById('box') box.onmousemove = function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` } </script>
效果是這樣的:閉包
在上邊的場景下,咱們不但願觸發一次就執行一次,這就要用到防抖或節流。下面咱們看一下它們能爲咱們作什麼吧。app
函數防抖,這裏的抖動就是執行的意思,而通常的抖動都是持續的,屢次的。假設函數持續屢次執行,
咱們但願讓它冷靜下來再執行。也就是當持續觸發事件的時候,函數是徹底不執行的,等最後一次觸發結束的
一段時間以後,再去執行。先看一下效果:函數
分解一下需求:性能
那麼怎麼實現上述的目標呢?咱們先看這一點:在不觸發的一段時間以後再執行,那就須要個定時器呀,定時器裏面調用咱們要執行的函數,將arguments傳入。
封裝一個函數,讓持續觸發的事件監聽是咱們封裝的這個函數,將目標函數做爲回調(func)傳進去,等待一段時間事後執行目標函數this
function debounce(func, delay) { return function() { setTimeout(() => { func.apply(this, arguments) }, delay) } }
第二點實現了,再看第一點:持續觸發不執行。咱們先思考一下,是什麼讓咱們的函數執行了呢?是上邊的setTimeout。OK,那如今的問題就變成了
持續觸發,不能有setTimeout。這樣直接在事件持續觸發的時候,清掉定時器就行了。spa
function debounce(func, delay) { let timeout return function() { clearTimeout(timeout) // 若是持續觸發,那麼就清除定時器,定時器的回調就不會執行。 timeout = setTimeout(() => { func.apply(this, arguments) }, delay) } }
用法:3d
box.onmousemove = debounce(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)
節流的意思是讓函數有節制地執行,而不是毫無節制的觸發一次就執行一次。什麼叫有節制呢?就是在一段時間內,只執行一次。
一樣,咱們分解一下:
效果是這樣的:
思考一下,持續觸發,並不會執行,可是到時間了就會執行。抓取一個關鍵的點:就是執行的時機。
要作到控制執行的時機,咱們能夠經過一個開關,與定時器setTimeout結合完成。
函數執行的前提條件是開關打開,持續觸發時,持續關閉開關,等到setTimeout到時間了,再把開關打開,函數就會執行了。
咱們看一下代碼怎麼實現:
function throttle(func, deley) { let run = true return function () { if (!run) { return // 若是開關關閉了,那就直接不執行下邊的代碼 } run = false // 持續觸發的話,run一直是false,就會停在上邊的判斷那裏 setTimeout(() => { func.apply(this, arguments) run = true // 定時器到時間以後,會把開關打開,咱們的函數就會被執行 }, deley) } }
調用的時候:
box.onmousemove = throttle(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)
這樣,就實現了節流,節流還能夠用時間間隔去控制,就是記錄上一次函數的執行時間,與當前時間做比較,若是當前時間與上次執行時間的時間差大於一個值,就執行。
說明一下節流時,後面操做中應該是由於run=false 因此才直接return,可是不是在return以前let run=ture直接覆蓋掉以前的false
這裏能夠看一下throttle函數內部,和函數調用的時候。首先看函數內部,分解一下結構:
function throttle(func, deley) { return function () { // 執行func } }
那調用時候呢?也分解一下:
throttle(function () { // 目標函數內容 }, 1000)
這裏throttle函數執行的結果是其內部return的function的調用。也就是說鼠標通過的事件監聽其實是這個被return的function,不斷持續觸發的是它,而throttle函數只是提供了一個做用域,內部用閉包聲明瞭一個run的開關變量,因爲閉包的存在,run這個變量會一直存在不被銷燬,而let run = true只在這個閉包(能夠理解爲做用域)內只聲明瞭一次,但它不會被持續執行,因此return的函數內部的判斷不會被它覆蓋掉。根據打印結果能夠看出,事實確實是如此:
防抖和節流巧妙地用了setTimeout,來控制函數執行的時機,優勢很明顯,能夠節約性能,不至於屢次觸發複雜的業務邏輯而形成頁面卡頓。
歡迎關注個人公衆號: 一口一個前端,不按期分享我所理解的前端知識