在前端開發的過程當中,咱們常常會須要綁定一些持續觸發的事件,如 resize、scroll、mousemove 等等,但在事件持續觸發期間頻繁的執行函數在很大程度上形成了沒必要要的浪費。javascript
一般這種狀況下咱們通常使用防抖和節流是這兩種解決方案。html
讓咱們先來看看在事件持續觸發的過程當中頻繁執行函數是怎樣的一種狀況。前端
html 文件中代碼以下java
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div> <script> let num = 1; let content = document.getElementById('content'); function count() { content.innerHTML = num++; }; content.onmousemove = count; </script>
在上述代碼中,div 元素綁定了 mousemove 事件,當鼠標在 div(灰色)區域中移動的時候會持續地去觸發該事件致使頻繁執行函數。效果以下app
能夠看到,在沒有經過其它操做的狀況下,函數被頻繁地執行致使頁面上數據變化特別快。因此,接下來讓咱們來看看防抖和節流是如何去解決這個問題的。函數
所謂防抖,就是指觸發事件後在 n 秒內函數只能執行一次,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。this
防抖函數分爲非當即執行版和當即執行版。code
非當即執行版:htm
function debounce(func, wait) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args) }, wait); } }
非當即執行版的意思是觸發事件後函數不會當即執行,而是在 n 秒後執行,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。blog
咱們依舊使用上述綁定 mousemove 事件的例子,經過上面的防抖函數,咱們能夠這麼使用
content.onmousemove = debounce(count,1000);
效果以下
能夠看到,在觸發事件後函數 1 秒後才執行,而若是我在觸發事件後的 1 秒內又觸發了事件,則會從新計算函數執行時間。
上述防抖函數的代碼還須要注意的是 this 和 參數的傳遞
let context = this; let args = arguments;
防抖函數的代碼使用這兩行代碼來獲取 this 和 參數,是爲了讓 debounce 函數最終返回的函數 this 指向不變以及依舊能接受到 e 參數。
當即執行版:
function debounce(func,wait) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); let callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait) if (callNow) func.apply(context, args) } }
當即執行版的意思是觸發事件後函數會當即執行,而後 n 秒內不觸發事件才能繼續執行函數的效果。
使用方法同上,效果以下
在開發過程當中,咱們須要根據不一樣的場景來決定咱們須要使用哪個版本的防抖函數,通常來說上述的防抖函數都能知足大部分的場景需求。但咱們也能夠將非當即執行版和當即執行版的防抖函數結合起來,實現最終的雙劍合璧版的防抖函數。
雙劍合璧版:
/** * @desc 函數防抖 * @param func 函數 * @param wait 延遲執行毫秒數 * @param immediate true 表當即執行,false 表非當即執行 */ function debounce(func,wait,immediate) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { let callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(() => { func.apply(context, args) }, wait); } } }
所謂節流,就是指連續觸發事件可是在 n 秒中只執行一次函數。 節流會稀釋函數的執行頻率。
對於節流,通常有兩種方式能夠實現,分別是時間戳版和定時器版。
時間戳版:
function throttle(func, wait) { var previous = 0; return function() { let now = Date.now(); let context = this; let args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } }
使用方式以下
content.onmousemove = throttle(count,1000);
效果以下
能夠看到,在持續觸發事件的過程當中,函數會當即執行,而且每 1s 執行一次。
定時器版:
function throttle(func, wait) { let timeout; return function() { let context = this; let args = arguments; if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } }
使用方式同上,效果以下
能夠看到,在持續觸發事件的過程當中,函數不會當即執行,而且每 1s 執行一次,在中止觸發事件後,函數還會再執行一次。
咱們應該能夠很容易的發現,其實時間戳版和定時器版的節流函數的區別就是,時間戳版的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候。
一樣地,咱們也能夠將時間戳版和定時器版的節流函數結合起來,實現雙劍合璧版的節流函數。
雙劍合璧版:
/** * @desc 函數節流 * @param func 函數 * @param wait 延遲執行毫秒數 * @param type 1 表時間戳版,2 表定時器版 */ function throttle(func, wait ,type) { if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }