窗口的resize、scroll、輸入框內容校驗等操做時,若是這些操做處理函數是較爲複雜或頁面頻繁重渲染等操做時,在這種狀況下若是事件觸發的頻率無限制,會加劇瀏覽器的負擔,致使用戶體驗很是糟糕。此時咱們能夠採用debounce(防抖)和throttle(節流)的方式來減小觸發的頻率,同時又不影響實際效果。javascript
eg:搜索框的請求優化,輸入搜索詞條須要當即觸發搜索請求時,防抖和節流能夠將多個請求合併爲一個請求php
github筆記傳送門,走過路過 star 一個?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> var num = 1; var content = document.getElementById('content'); function count() { content.innerHTML = num++; }; content.onmousemove = count; </script>
複製代碼
debounce(防抖),簡單來講就是防止抖動。git
當持續觸發事件時,debounce 會合併事件且不會去觸發事件,當必定時間內沒有觸發再這個事件時,才真正去觸發事件。github
非當即執行版的意思是觸發事件後函數不會當即執行,而是在 n 秒後執行,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。瀏覽器
const debounce = (func, wait, ...args) => {
let timeout;
return function(){
const context = this;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
},wait);
}
}
複製代碼
如此調用:app
content.onmousemove = debounce(count,1000);
複製代碼
當即執行版的意思是觸發事件後函數會當即執行,而後 n 秒內不觸發事件才能繼續執行函數的效果。函數
const debounce = (func, wait, ...args) => {
let timeout;
return function(){
const context = this;
if (timeout) cleatTimeout(timeout);
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
},wait)
if(callNow) func.apply(context,args)
}
}
複製代碼
/** * @desc 函數防抖 * @param func 函數 * @param wait 延遲執行毫秒數 * @param immediate true 表當即執行,false 表非當即執行 */
function debounce(func,wait,immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
複製代碼
throttle(節流),當持續觸發事件時,保證隔間時間觸發一次事件。post
持續觸發事件時,throttle 會合並必定時間內的事件,並在該時間結束時真正去觸發一次事件。
在持續觸發事件的過程當中,函數會當即執行,而且每 1s 執行一次。
const throttle = (func, wait, ...args) => {
let pre = 0;
return function(){
const context = this;
let now = Date.now();
if (now - pre >= wait){
func.apply(context, args);
pre = Date.now();
}
}
}
複製代碼
在持續觸發事件的過程當中,函數不會當即執行,而且每 1s 執行一次,在中止觸發事件後,函數還會再執行一次。
const throttle = (func, wait, ...args) => {
let timeout;
return function(){
const context = this;
if(!timeout){
timeout = setTimeout(() => {
timeout = null;
func.apply(context,args);
},wait)
}
}
}
複製代碼
其實時間戳版和定時器版的節流函數的區別就是,時間戳版的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候。
/** * @desc 函數節流 * @param func 函數 * @param wait 延遲執行毫秒數 * @param type 1 表時間戳版,2 表定時器版 */
function throttle(func, wait ,type) {
if(type===1){
var previous = 0;
}else if(type===2){
var timeout;
}
return function() {
var context = this;
var args = arguments;
if(type===1){
var now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}else if(type===2){
if (!timeout) {
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}
複製代碼
/** * underscore 防抖函數,返回函數連續調用時,空閒時間必須大於或等於 wait,func 纔會執行 * * @param {function} func 回調函數 * @param {number} wait 表示時間窗口的間隔 * @param {boolean} immediate 設置爲ture時,是否當即調用函數 * @return {function} 返回客戶調用函數 */
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
// 如今和上一次時間戳比較
var last = _.now() - timestamp;
// 若是當前間隔時間少於設定時間且大於0就從新設置定時器
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
// 不然的話就是時間到了執行回調函數
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
// 得到時間戳
timestamp = _.now();
// 若是定時器不存在且當即執行函數
var callNow = immediate && !timeout;
// 若是定時器不存在就建立一個
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
// 若是須要當即執行函數的話 經過 apply 執行
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
複製代碼
/** * underscore 節流函數,返回函數連續調用時,func 執行頻率限定爲 次 / wait * * @param {function} func 回調函數 * @param {number} wait 表示時間窗口的間隔 * @param {object} options 若是想忽略開始函數的的調用,傳入{leading: false}。 * 若是想忽略結尾函數的調用,傳入{trailing: false} * 二者不能共存,不然函數不能執行 * @return {function} 返回客戶調用函數 */
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 以前的時間戳
var previous = 0;
// 若是 options 沒傳則設爲空對象
if (!options) options = {};
// 定時器回調函數
var later = function() {
// 若是設置了 leading,就將 previous 設爲 0
// 用於下面函數的第一個 if 判斷
previous = options.leading === false ? 0 : _.now();
// 置空一是爲了防止內存泄漏,二是爲了下面的定時器判斷
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 得到當前時間戳
var now = _.now();
// 首次進入前者確定爲 true
// 若是須要第一次不執行函數
// 就將上次時間戳設爲當前的
// 這樣在接下來計算 remaining 的值時會大於0
if (!previous && options.leading === false) previous = now;
// 計算剩餘時間
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 若是當前調用已經大於上次調用時間 + wait
// 或者用戶手動調了時間
// 若是設置了 trailing,只會進入這個條件
// 若是沒有設置 leading,那麼第一次會進入這個條件
// 還有一點,你可能會以爲開啓了定時器那麼應該不會進入這個 if 條件了
// 其實仍是會進入的,由於定時器的延時
// 並非準確的時間,極可能你設置了2秒
// 可是他須要2.2秒才觸發,這時候就會進入這個條件
if (remaining <= 0 || remaining > wait) {
// 若是存在定時器就清理掉不然會調用二次回調
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判斷是否設置了定時器和 trailing
// 沒有的話就開啓一個定時器
// 而且不能不能同時設置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
複製代碼