防抖和节流
其实好多地方都有接触到,一直没有时间做个小总结。关键是在于一定时间内的延迟事件的触发。之前项目中的轮询和长链接其实大都是运用到了类似的方法。
窗口resize,鼠标touchmove事件,scroll,以及类似keypress这样的密集监听事件的变动,都会产生大量的性能消耗。
-
常规的防抖节流方案debounce,
-
窗口事件,按压,touch移动等相关的事件都可以用来进行优化,防止密集触发导致页面卡死。其中关于context和arguments的介绍可以参考一下JS中的arguments参数,我觉得这样理解比较好.
-
作用是在短时间内多次触发同一个函数,只执行最后一次,或者只在开始时执行。
// debounce 函数接受一个函数和延迟执行的时间作为参数 /* *@fn 实际调用的回调函数体 *@delay 触发回调事件的延迟时间 */ function debounce(fn, delay){ let timer = null; return function() { // 获取函数的作用域和变量 let context = this; // 函数的隐式参数一般有两个,一个是this,未来调用函数的实际上下文作用域,还有一个就是封装实参的对象arguments。 let args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, delay) } }- 优化后的debounce,优化添加一个立即执行函数参数
function debounce(func, delay, immediate){ var timer = null; return function(){ var context = this; var args = arguments; if(timer) clearTimeout(timer); if(immediate){ var doNow = !timer; timer = setTimeout(function(){ timer = null; },delay); if(doNow){ func.apply(context,args); } }else{ timer = setTimeout(function(){ func.apply(context,args); },delay); } } }内部运行原理
- 第一次进入监听函数体,给闭包一个初始化timer为null,内部作为一个个闭包返回体函数,其内部变量会进行内存驻留,而外部的变量无法影响到当前的闭包作用下的timer,也就是每次触发监听时,tiemr永远都是上次返回的值。此时doNow为true,回调函数第一次进入就会执行。
- setTimeout函数会生产一个计数器ID返回,每次内部clearTimeout清除之后,就相当于返回timer为整形的参数,上次的回调函数事件也会被阻止。
- 在频繁触发监听事件时并且时间间隔没有超过delay,闭包函数体第三行的timer会被clear,并且此时timer为返回的是整形,再次触发immediate参数时,doNow=!计数Id 返回false,所以回调函数并不会执行
- 如果没有传immediate参数,直接走延时函数,和上面的方案一致。
-
节流:类似于防抖,节流是在一段时间内只允许函数执行一次。 input监听,输入框智能提示以及像高德地图的POI智能检索地区,密码输入提示等的相关案例都是这么实现的。
时间戳实现:
var throttle = function(func, delay){
var prev = Date.now();
return function(){
var context = this;
var args = arguments;
var now = Date.now();
if(now-prev>=delay){
func.apply(context,args);
prev = Date.now();
}
}
}
当监听密集时间间隔小于自己设置delay时间,由于每次的触发监听时,prev时间是进入函数时,初始化的时间。闭包内的函数now-prev < delay不会执行,这样就可以做到限流。
定时器实现:
var throttle = function(func, delay){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context, args);
timer = null;
},delay);
}
}
}
两次实现的原理基本类似,时间戳节流可以做到第一次先执行,定时器的方式是等延迟结束后执行。具体怎么使用,还是看具体的业务。其中和核心思想,还是在于闭包。
闭包
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。可以看这一篇js中的闭包