一在辆、前言
在前端開發(fā)中會遇到一些頻繁的事件觸發(fā)规揪,比如:
- window 的 resize工闺、scroll
- mousedown乍赫、mousemove
- keyup、keydown
為此陆蟆,我們舉個示例代碼來了解事件如何頻繁的觸發(fā):
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</title>
<style>
#container{
width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
<script>
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = getUserAction;
</script>
</html>
因為這個例子很簡單雷厂,所以瀏覽器完全反應的過來,可是如果是復雜的回調(diào)函數(shù)或是 ajax 請求呢叠殷?假設 1 秒觸發(fā)了 60 次改鲫,每個回調(diào)就必須在 1000 / 60 = 16.67ms 內(nèi)完成,否則就會有卡頓出現(xiàn)
為了解決這個問題,一般有兩種解決方案:
debounce 防抖
throttle 節(jié)流
二像棘、debounce原理
防抖的原理就是:你盡管觸發(fā)事件稽亏,但是我一定在事件觸發(fā) n 秒后才執(zhí)行,如果你在一個事件觸發(fā)的 n 秒內(nèi)又觸發(fā)了這個事件缕题,那我就以新的事件的時間為準截歉,n 秒后才執(zhí)行
function debounce(callback,wait=3000) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(callback,wait);
}
}
上述的方法看似沒有什么問題,但是參考
lodash
烟零、underscore
你會發(fā)現(xiàn)這類庫封裝的非常完善
- 函數(shù)中this指向
- 事件對象
- 函數(shù)立即執(zhí)行
根據(jù)以上的幾個問題我們進行進一步的完善
1瘪松、this指向
function debounce(callback,wait=3000) {
var timeout;
return function() {
var context = this;
clearTimeout(timeout);
timeout = setTimeout(function(){
callback.apply(context)
},wait);
}
}
這里解釋一個為什么
var context = this;
要加一行這樣的代碼,難道默認不是指向window
嗎锨阿?因為在JS的嚴格模式下this會指向undefined
宵睦,另外在Node環(huán)境中是沒有window對象的,其實我們這里代碼也不是特別嚴謹墅诡,后續(xù)會繼續(xù)完善
'use strict'
function debounce(callback,wait=3000) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(function(){
callback()
},wait);
}
}
function getUserAction(){
console.log(1,this) // 1 undefined
}
document.onmousemove = debounce(getUserAction, 1000);
2壳嚎、事件對象
上述方法中是無法訪問到
event
對象的,因此我們需要再次完善
'use strict'
function debounce(callback,wait=3000) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(function(){
callback()
},wait);
}
}
function getUserAction(e){
console.log(1,this,e) // 1 undefined undefined
}
document.onmousemove = debounce(getUserAction, 1000);
完善
event
對象
'use strict'
function debounce(callback,wait=3000) {
var timeout;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function(){
callback.apply(context,args);
},wait);
}
}
3书斜、立即執(zhí)行
什么是立即執(zhí)行诬辈?簡單的來說就是我希望第一次執(zhí)行的時候沒有時間的延遲,第二次的時候才會有時間的延遲荐吉。如果你聽不懂我說的話,那么可以告訴你就是類似于Vue的
watch
方法的immediate
屬性口渔。默認第一次會進行監(jiān)聽一樣
"use strict";
function debounce(callback, wait = 3000, immediate) {
var timeout,result
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 判斷是否執(zhí)行過
var flag = !timeout;
timeout = setTimeout(function () {
callback.apply(context, args);
}, wait);
if (flag) callback.apply(context, args);
} else {
timeout = setTimeout(function () {
callback.apply(context, args);
}, wait);
}
};
}
三样屠、throttle原理
節(jié)流的原理也很簡單,假設原本1秒會執(zhí)行100次的函數(shù)缺脉,我們可以控制到1秒執(zhí)行10次痪欲。
方案:當觸發(fā)事件的時候,我們?nèi)〕霎斍暗臅r間戳攻礼,然后減去之前的時間戳(最一開始值設為 0 )业踢,如果大于設置的時間周期,就執(zhí)行函數(shù)礁扮,然后更新時間戳為當前的時間戳知举,如果小于,就不執(zhí)行
"use strict";
function throttle(callback, wait = 300) {
var context,
args,
firstTime = 0;
return function () {
var iNow = +new Date();
context = this;
args = arguments;
if (iNow - firstTime > wait) {
console.log(111);
callback.apply(context, args);
firstTime = iNow;
}
};
}
接下來我們進行優(yōu)化太伊,原因是如果我們在最后一次停止觸發(fā)的時候如果時間差沒有達到300ms那么最后一次是不執(zhí)行的雇锡,因此我們需要結(jié)合定時器來進行優(yōu)化
"use strict";
function throttle(callback, delay, immediate=true) {
var timer,context,iNow,firstTime = +new Date(),args = [];
return function() {
clearTimeout(timer);
context = this;
iNow = +new Date();
args = Array.prototype.slice.call(arguments);
// 判斷是否是第一次執(zhí)行
if(immediate) {
immediate = false;
callback.apply(context,args);
} else {
// 第二次執(zhí)行的時候判斷時間差
if(iNow - firstTime > delay) {
firstTime = iNow;
callback.apply(context,args);
} else {
// 判斷是否是最后一次執(zhí)行
timer = setTimeout(function(){
callback.apply(context,args);
},delay)
}
}
}
}