防抖
防抖是js優(yōu)化的重要的一部分,也是面試中手寫代碼最??嫉念}目。那么我們?yōu)槭裁匆蓝??防抖是什么意思?br>
比如我們在監(jiān)聽onkeyup事件中,監(jiān)聽input中輸入的文字,我們可以在console中可以看到input的文字打印。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input id="input1" type="text">
</body>
<script src="index.js"></script>
</html>
const input1 = document.getElementById('input1');
// console.log(input1);
input1.addEventListener('keyup', function() {
console.log(input1.value);
})

非常簡單。但是每打一個字就監(jiān)聽到了,就很耗性能。
有人就問了,誒?那我能不能等我打完一小部分字段了以后,然后瀏覽器才去監(jiān)聽呢?
又有人說了,用
setTimeout不就完了,過個一秒再監(jiān)聽。所以,防抖這個詞就來了。
所以我們在剛才的index.js中的代碼中,input1的監(jiān)聽中,加入一個定時器試試:
const input1 = document.getElementById('input1');
// console.log(input1);
let timer = null;
input1.addEventListener('keyup', function() {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log(input1.value);
// 清空定時器
timer = null;
}, 500)
})
先演示一下:

可以看到,當我直接依次輸入123456的時候,停頓了一會,然后瀏覽器才監(jiān)聽到我輸入了,并打印123456,接著我又輸入了789停止,半秒后,又打印了123456789,很妙。就是說,你一直輸一直輸,只要你中間輸入的間斷不超過500毫秒(咱們自己定的時間),它就不會出來,就一直等著你輸,什么時候你輸入的間斷,暫停的或者停止的時間超過500毫秒,它才會把最終的結(jié)果打印出來。
用很通俗的話說一下這里面的原理:
①先弄個定時器,監(jiān)聽到keyup的時候看一下現(xiàn)在定時器存在嗎?不存在,是null,那我們不管,往下走。
②接下來給定時器賦值setTimeout的異步任務(wù),這個異步任務(wù)是500毫秒之后執(zhí)行,執(zhí)行的結(jié)果就是打印當前的input中的value。打印完之后,要清空定時器。
流程是醬的:
①輸入個1,停止:
timer為null,if語句不觸發(fā),往下進行,500毫秒后輸出value,然后清空timer,這是第一次,而且只輸入一個1的情況下,比較簡單。
②輸入123,停止:
timer為null,if語句不觸發(fā),往下進行,500毫秒之后執(zhí)行,timer有值了,這個時候,我們立馬就輸入2了,keyup又監(jiān)聽了,這時候500毫秒還沒有到,監(jiān)聽之后,timer是有值的,if條件判斷為true,因為之前輸入1的時候,timer就有值了,所以我們把之前輸入1的那個timer給清除掉,然后下面重新設(shè)置timer,500毫秒之后執(zhí)行,但是還沒開始執(zhí)行的時候,又輸入3了,立馬又會出現(xiàn)keyup事件,進入if,有timer,這個timer是輸入2的時候定義的,然后又清空了,重新設(shè)置了定時任務(wù),500毫秒只有執(zhí)行,這時候已經(jīng)沒有值進來了,所以500毫秒到了以后,輸出剛才的123。接下來如果再輸入一個4,是和第一次輸入一個1是一樣的。
寫的比較啰嗦,但是這就是傳說中的防抖。
但是有個問題,就是我們?yōu)榱艘粋€input1,就寫了這么多行代碼,感覺好麻煩啊,如果我們又10個input,我們是不是得寫10個這樣的監(jiān)聽函數(shù)?
所以新的問題就來了,我們需要改進一下我們的防抖函數(shù),將它簡單的封裝一下:
我們需要將防抖函數(shù)封裝成一個公共方法,那么這個方法的參數(shù)應(yīng)該有個函數(shù)fn,和一個delay時間延遲,然后最終要返回一個函數(shù)。
代碼如下:
// 防抖
function debounce(fn, delay = 500) {
// timer 是在閉包中的
let timer = null;
return function() {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
// 清空定時器
timer = null;
}, delay)
}
}
input1.addEventListener('keyup', debounce(function() {
console.log(input1.value);
}), 600);
?
節(jié)流
但我們拖拽一個元素時,要隨時拿到該元素被拖拽的位置,直接用drag事件,則會頻發(fā)觸發(fā),很容易導致卡頓。節(jié)流的意思是:無論拖拽的速度有多快,都會每隔100ms觸發(fā)一次。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#div1 {
border: 1px solid #ccc;
width: 200px;
height: 100px;
}
</style>
</head>
<body>
<div id="div1" draggable="true">可拖拽</div>
</body>
<script src="index.js"></script>
</html>
const div1 = document.getElementById('div1');
// console.log(div1);
div1.addEventListener('drag', function(e) {
console.log(e.offsetX, e.offsetY);
})

可以看到,
drag基本上隨時都在觸發(fā),這時候我們?nèi)绻僬{(diào)用別的js的功能,很容易造成負載驗證,卡死。所以我們和防抖一樣,加個
setTimeout:
const div1 = document.getElementById('div1');
// console.log(div1);
let timer = null;
div1.addEventListener('drag', function(e) {
if(timer) {
return;
}
timer = setTimeout(() => {
console.log(e.offsetX, e.offsetY);
// 清空定時器
timer = null;
}, 100)
})
只不過這次如果不存在timer,我們直接return出去,就不管了,自生自滅,不響應(yīng)了。知道100毫秒之后,打印了,并且timer被賦值為null,然后才會有timer的下一次setTimeout...具體的流程就不說了,和防抖是一樣的。
同樣,我們需要將節(jié)流函數(shù)封裝一下,工具化:
// 節(jié)流
function throttle(fn, delay = 100) {
let timer = null;
return function() {
if(timer) {
return;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay)
}
}
div1.addEventListener('drag', throttle(function(e) {
console.log(e.offsetX, e.offsetY);
}), 200)