前言
为什么我们要在项目中使用防抖???
随着计算机的不断发展,浏览器也在飞速的更新迭代。从刚开始的只是展示图文排版的网页,到现在人机交互和科学计算的动态项目。不同的时代同样也推动着思想的发展,到目前为止的网页,直面用户最多的也就是网站的视觉特效和交互性能。性能提升更是作为前端工程师都要关注的一个问题。防抖作为代码层面的提升性能的工具是很有必要学一下的。
防抖能做的事
在前端开发中会遇到一些频繁的事件触发,比如
- window的resize、scroll事件等
- mousedown、mousemove事件
- 搜索框的input事件等
上面这些事件频繁的触发会引起页面的抖动,如果涉及到请求,同样也会发生请求爆炸。在前端社会需要的有人来站出来拯救这个战乱的时候,防抖来了。
防抖的实现原理
你尽管触发事件,但是我一定在事件触发n秒后才执行,如果你在一个事件触发的n秒内又触发了这个事件,那么我就以新的事件的时间为准,n秒后再执行。总之,就是要等这个事件触发完n秒后再触发事件。
underscore防抖的实现
防抖就不一步一步的实现了,有需求的话请欣赏冴羽大大的 跟着underscore学防抖文章,每一步的实现讲的非常清晰,同时也感谢冴羽大大让我又明白了一项技术。
实现防抖的注意点。
- 由于是事件处理,debounce函数必须要接受一个函数作为参数,并返回一个函数,第二个参数为触发事件所等待的时间。其次,既然等待就必须要有定时器,当每次触发事件的时候首先要清除定时器。
- 当绑定时间处理的时候,此时的this应该是事件源本身。
- event事件对象:当我们触发事件的时候,相应的我们会得到event事件对象。
- 立即执行,有时候我们想当我们首次触发事件的时候会立即执行,以后连续的操作会出现防抖操作。立即执行我们的防抖函数要加上第三个参数判断是否要立即执行。
- 返回值:如果我们的函数中是有返回值的,当immediate为false的时候,因为使用了setTimeout,最后return的时候一直是undefined,所以,只在immediate为true的时候返回值。
- 取消:有时候我们需要这样的一个需求,当我的防抖时间间隔很长的时候。在这个漫长的等待过程中,我想要取消防抖。再次执行然后触发。
根据上面这些实现防抖的注意点,我们的underscore防抖诞生了。
function debounce(fn,time,immediate) {
var timeout,result;
var debounced = function (){
var context = this;
var args = Array.prototype.slice.call(arguments);
if(timeout){
clearTimeout(timeout);
}
if(immediate){
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
},time)
if(callNow){
result = fn.apply(context,args);
}
} else{
timeout = setTimeout(function(){
fn.apply(context,args);
},time)
}
return result;
}
debounced.cancel = function(){
clearTimeout(timeout);
timeout = null;
}
return debounced;
}
说说我碰到的问题
立即执行
underscore防抖的立即执行的功能很简单的可以看出避免用户等候的时间太长,所做的优化,但是会发现当频繁触发事件的时候,立即执行了一次,后续就不会触发第二次。在鼠标移动事件的可以很完美,但是到搜索框上就不那么美妙了。
右边的数字是显示input触发的次数,这就可以看出,我按了那么多,它只执行了开始的一次,最后的一次并没有执行。
个人观点
在立即执行这个功能上,存在着不同需求的原因,根据不同的需求去选择合适的功能。比如在鼠标划动上可以添加上立即触发功能而放弃最后一次的触发,在搜索框上面则不需要立即触发功能,用最后一次的触发来实现防抖功能。
取消
取消,优化了用户等待时间过长这样一个问题,但同样也会出现一些小问题,这个问题出现在不是立即执行上面,就上面的input输入框而言,当我在漫长的等待过程中,点击了取消,这个事件就永远不会触发,有可以我们想要的结果是当我们点击了取消,事件直接触发了,多好。
因为上面这个需求,我在underscore上面改动了一点点,达到了我想要的目的。
写出自己underscore防抖
跟紧时代的潮流,我自己写的underscore防抖是以ES6的calss写的,同样也是映着模块化开发的思想。话不多说,先把代码放上。
class Debounce {
constructor(fn,time = 1000,immediate = false){
if(typeof fn !== "function"){
throw new Error("first param is not function");
}
this.fn = fn;
this.time = time;
this.immediate = immediate;
this.context;
this.args;
this.timeout;
this.result;
}
debounced(){
let _self = this;
return function(...args) {
_self.context = this;
_self.args = args;
if (_self.timeout) {
clearTimeout(_self.timeout);
}
if (_self.immediate) {
let callNow = !_self.timeout;
_self.timeout = setTimeout(() => {
_self.timeout = null;
},_self.time);
if(callNow) {
_self.result = _self.fn.apply(_self.context, _self.args);
}
} else {
_self.timeout = setTimeout( () => {
_self.fn.apply(_self.context,_self.args);
},_self.time)
}
return _self.result;
}
}
cancel(){
if(!this.immediate) {
this.fn.apply(this.context,this.args);
}
clearTimeout(this.timeout);
this.timeout = null;
}
}
使用方法
var box = document.getElementById("box");
var input = document.getElementById("input");
var span = document.getElementById("span");
function func(e) {
console.log(e);
console.log(this);
box.innerHTML = count++;
return 123;
};
function test(e) {
console.log(e);
span.innerHTML = count++;
}
//在这里在这里
const inputDebounce = new Debounce(test,10000);
const mouseDebounce = new Debounce(func,10000,true);
input.oninput = inputDebounce.debounced();
box.onmousemove = mouseDebounce.debounced();
class中的constructor方法参数采用参数默认值的形式,把time和immediate写成默认值,并在代码中对第一个参数是不是function做了检测。然后把所有的所要用到的变量值暴露到了实例属性上,underscore防抖是事件函数的this和arguments都在内部。 暴露出来的目的就是为了实现取消功能。在cancel方法中判断如果当前不是立即执行的行为,当触发取消事件的时候就必须立即执行事件函数。
以input为例,代码如下
document.getElementById("button").addEventListener('click', function () {
inputDebounce.cancel();
})
把等待时间改为10000试验一下。
成功了,此时不是立即执行状态,在漫长的等待中当我点击close按钮的时候,1出现了,开心。