防抖和節(jié)流
防抖
在準(zhǔn)備執(zhí)行某項(xiàng)操作時(shí),預(yù)設(shè)一個(gè)間隔時(shí)間,然后計(jì)時(shí),達(dá)到預(yù)設(shè)的時(shí)間間隔之后才執(zhí)行邏輯;
在沒到達(dá)間隔時(shí)間這段時(shí)間內(nèi),如果有多次重復(fù)操作,那么后面的操作會(huì)頂替掉前面的計(jì)時(shí)任務(wù),重新開始計(jì)時(shí)。
應(yīng)用場景: 輸入框輸入內(nèi)容過程中去服務(wù)器查詢數(shù)據(jù),不能每次input都去查詢,所以只有當(dāng)用戶輸入停頓一定時(shí)間間隔時(shí),認(rèn)為用戶希望獲得查詢結(jié)果。
代碼如下:
/**
* 防抖: 重復(fù)操作重置定時(shí)器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
// 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
// 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
isExecuted = false;
// 定時(shí)器存在時(shí),清空并重建
// 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的兩種情況
// 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
// 2. 完成了一次執(zhí)行,timer被釋放
// 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
// timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
!!immediate && callback.apply(context, args);
isExecuted = true; // 標(biāo)記為已執(zhí)行
run();
}
}
}
測試代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>節(jié)流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
}
</style>
</head>
<body>
<input id="input" type="text">
<span>input輸入結(jié)果</span>
<script>
window.onload = () => {
var inputEl = document.getElementById('input');
inputEl.addEventListener('input', debounce(function(e) {
this.nextElementSibling.textContent = this.value
}, 1000, true));
}
/**
* 防抖: 重復(fù)操作重置定時(shí)器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
// 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
// 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
isExecuted = false;
// 定時(shí)器存在時(shí),清空并重建
// 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的兩種情況
// 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
// 2. 完成了一次執(zhí)行,timer被釋放
// 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
// timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
!!immediate && callback.apply(context, args);
isExecuted = true; // 標(biāo)記為已執(zhí)行
run();
}
}
}
</script>
</body>
</html>
節(jié)流
當(dāng)短時(shí)間內(nèi)重復(fù)執(zhí)行某項(xiàng)操作時(shí),予以忽略,只執(zhí)行一次;知道執(zhí)行完成之后才會(huì)重新添加執(zhí)行能力。
實(shí)現(xiàn)原理: 維護(hù)一個(gè)定時(shí)器,每次執(zhí)行操作是都判斷定時(shí)器是否存在,如果定時(shí)器存在,直接return;
如果定時(shí)器不存在,則創(chuàng)建定時(shí)器,定時(shí)器到期后執(zhí)行,并清除定時(shí)器和定時(shí)器標(biāo)志
代碼如下:
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
// 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
if (!immediate) callback.apply(context, args)
clearTimeout(timer) // 清除定時(shí)器,
timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
}, delay)
}
return function() {
// 存儲(chǔ)上下文和實(shí)參列表
context = this;
args = arguments;
// 當(dāng)前如果有定時(shí)器任務(wù),則取消操作
if (timer) return
// 如果需要立即執(zhí)行,則執(zhí)行
if (immediate) callback.apply(context, arguments)
// 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
run()
}
}
測試:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>節(jié)流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
window.onload = () => {
var containerEl = document.getElementById('container');
var i = 0;
containerEl.addEventListener('mousemove', throttle((e) => {
containerEl.textContent = i++
}, 200, true));
}
/**
* 節(jié)流
*/
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
// 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
!immediate && callback.apply(context, args);
clearTimeout(timer) // 清除定時(shí)器,
timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
}, delay)
}
return function() {
// 存儲(chǔ)上下文和實(shí)參列表
context = this;
args = arguments;
if (timer) return; // 當(dāng)前如果有定時(shí)器任務(wù),則取消操作
!!immediate && callback.apply(context, args); // 如果需要立即執(zhí)行,則執(zhí)行
run() // 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
}
}
</script>
</body>
</html>
完整代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>節(jié)流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
margin-top: 20px;
}
</style>
</head>
<body>
<input id="input" type="text">
<span>input輸入結(jié)果</span>
<div id="container"></div>
<script>
window.onload = () => {
var inputEl = document.getElementById('input');
var containerEl = document.getElementById('container');
var i = 0;
inputEl.addEventListener('input', debounce(function(e) {
this.nextElementSibling.textContent = this.value
}, 1000, true));
containerEl.addEventListener('mousemove', throttle((e) => {
containerEl.textContent = i++
}, 200, true));
}
/**
* 防抖: 重復(fù)操作重置定時(shí)器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
// 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
// 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
isExecuted = false;
// 定時(shí)器存在時(shí),清空并重建
// 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的兩種情況
// 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
// 2. 完成了一次執(zhí)行,timer被釋放
// 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
// timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
!!immediate && callback.apply(context, args);
isExecuted = true; // 標(biāo)記為已執(zhí)行
run();
}
}
}
/**
* 節(jié)流
*/
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
// 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
!immediate && callback.apply(context, args);
clearTimeout(timer) // 清除定時(shí)器,
timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
}, delay)
}
return function() {
// 存儲(chǔ)上下文和實(shí)參列表
context = this;
args = arguments;
if (timer) return; // 當(dāng)前如果有定時(shí)器任務(wù),則取消操作
!!immediate && callback.apply(context, args); // 如果需要立即執(zhí)行,則執(zhí)行
run() // 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
}
}
</script>
</body>
</html>