一、前言
在前端开发中会遇到一些频繁的事件触发,比如:
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown
为此,我们举个示例代码来了解事件如何频繁的触发:
debounce
因为这个例子很简单,所以浏览器完全反应的过来,可是如果是复杂的回调函数或是 ajax 请求呢?假设 1 秒触发了 60 次,每个回调就必须在 1000 / 60 = 16.67ms 内完成,否则就会有卡顿出现
为了解决这个问题,一般有两种解决方案:
debounce 防抖
throttle 节流
二、debounce原理
防抖的原理就是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行
function debounce(callback,wait=3000) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(callback,wait);
}
}
上述的方法看似没有什么问题,但是参考
lodash
、underscore
你会发现这类库封装的非常完善
- 函数中this指向
- 事件对象
- 函数立即执行
根据以上的几个问题我们进行进一步的完善
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环境中是没有window对象的,其实我们这里代码也不是特别严谨,后续会继续完善
'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、立即执行
什么是立即执行?简单的来说就是我希望第一次执行的时候没有时间的延迟,第二次的时候才会有时间的延迟。如果你听不懂我说的话,那么可以告诉你就是类似于Vue的
watch
方法的immediate
属性。默认第一次会进行监听一样
"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) {
// 判断是否执行过
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原理
节流的原理也很简单,假设原本1秒会执行100次的函数,我们可以控制到1秒执行10次。
方案:当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行
"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;
}
};
}
接下来我们进行优化,原因是如果我们在最后一次停止触发的时候如果时间差没有达到300ms那么最后一次是不执行的,因此我们需要结合定时器来进行优化
"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);
// 判断是否是第一次执行
if(immediate) {
immediate = false;
callback.apply(context,args);
} else {
// 第二次执行的时候判断时间差
if(iNow - firstTime > delay) {
firstTime = iNow;
callback.apply(context,args);
} else {
// 判断是否是最后一次执行
timer = setTimeout(function(){
callback.apply(context,args);
},delay)
}
}
}
}