函数节流场景
例如:实现一个原生的拖拽功能(如果不用H5 Drag和Drop API),我们就需要一路监听mousemove事件,在回调中获取元素当前位置,然后重置dom的位置。如果我们不加以控制,每移动一定像素而出发的回调数量是会非常惊人的,回调中又伴随着DOM操作,继而引发浏览器的重排和重绘,性能差的浏览器可能会直接假死。这时,我们就需要降低触发回调的频率,比如让它500ms触发一次或者200ms,甚至100ms,这个阀值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能会假死,这时的解决方案就是函数节流【throttle】。函数节流的核心就是:让一个函数不要执行得太频繁,减少一些过快的调用来节流。
函数去抖场景
例如:对于浏览器窗口,每做一次resize操作,发送一个请求,很显然,我们需要监听resize事件,但是和mousemove一样,每缩小(或者放大)一次浏览器,实际上会触发N多次的resize事件,这时的解决方案就是节流【debounce】。函数去抖的核心就是:在一定时间段的连续函数调用,只让其执行一次
函数节流的实现
函数节流的第一种方案封装如下:
functionthrottleFunc(method,context){ clearTimeout(method.timer);//为什么选择setTimeout 而不是setIntervalmethod.timer = setTimeout(function(){ method.call(context); },100);}
看一个封装的demo
window.onscroll =function(){ throttleFunc(show);}functionshow(){console.log(1);}functionthrottleFunc(method){ clearTimeout(method.timer); method.timer = setTimeout(function(){ method(); },100);}
也可以使用闭包的方法对上面的函数进行再封装一次
functionthrottle(fn, delay){vartimer =null;returnfunction(){ clearTimeout(timer); timer = setTimeout(function(){ fn(); }, delay); };};
调用
varfunc = throttle(show,100);functionshow(){console.log(1);}window.onscroll =function(){ func();}
封装2
functionthrottle(fn, delay, runDelay){vartimer =null;vart_start;returnfunction(){vart_cur =newDate(); timer && clearTimeout(timer);if(!t_start) { t_start = t_cur; }if(t_cur - t_start >= runDelay) { fn(); t_start = t_cur; }else{ timer = setTimeout(function(){ fn(); }, delay); } }}
调用
varfunc = throttle(show,50,100);functionshow(){console.log(1);}window.onscroll =function(){ func();}
函数去抖的实现:
代码在underscore的基础上进行了扩充
// 函数去抖(连续事件触发结束后只触发一次)// sample 1: _.debounce(function(){}, 1000)// 连续事件结束后的 1000ms 后触发// sample 1: _.debounce(function(){}, 1000, true)// 连续事件触发后立即触发(此时会忽略第二个参数)_.debounce =function(func, wait, immediate){vartimeout, args, context, timestamp, result;varlater =function(){// 定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔 // 如果间隔为 wait(或者刚好大于 wait),则触发事件 varlast = _.now() - timestamp;// 时间间隔 last 在 [0, wait) 中 // 还没到触发的点,则继续设置定时器 // last 值应该不会小于 0 吧? if(last < wait && last >=0) { timeout = setTimeout(later, wait - last); }else{// 到了可以触发的时间点 timeout = null; // 可以触发了 // 并且不是设置为立即触发的 // 因为如果是立即触发(callNow),也会进入这个回调中 // 主要是为了将 timeout 值置为空,使之不影响下次连续事件的触发// 如果不是立即执行,随即执行 func 方法 if(!immediate) {// 执行 func 函数 result = func.apply(context, args);// 这里的 timeout 一定是 null 了吧 // 感觉这个判断多余了 if(!timeout) context = args =null; } } };// 嗯,闭包返回的函数,是可以传入参数的 returnfunction(){// 可以指定 this 指向 context =this; args =arguments;// 每次触发函数,更新时间戳 // later 方法中取 last 值时用到该变量 // 判断距离上次触发事件是否已经过了 wait seconds 了 // 即我们需要距离最后一次触发事件 wait seconds 后触发这个回调方法timestamp = _.now();// 立即触发需要满足两个条件 // immediate 参数为 true,并且 timeout 还没设置 // immediate 参数为 true 是显而易见的 // 如果去掉 !timeout 的条件,就会一直触发,而不是触发一次 // 因为第一次触发后已经设置了 timeout,所以根据 timeout 是否为空可以判断是否是首次触发 varcallNow = immediate && !timeout;// 设置 wait seconds 后触发 later 方法 // 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数) // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中 if(!timeout)// 设置了 timeout,所以以后不会进入这个 if 分支了 timeout = setTimeout(later, wait);// 如果是立即触发 if(callNow) {// func 可能是有返回值的 result = func.apply(context, args);// 解除引用 context = args =null; }returnresult; };};
节流函数
varthrottle =function(func, wait){vartimeout, context, args, startTime =Date.parse(newDate());returnfunction(){varcurTime =Date.parse(newDate());varremaining = wait - (curTime - startTime); context =this; args =arguments; clearTimeout(timeout);if(remaining <=0){ func.apply(context, args); startTime =Date.parse(newDate()); }else{ timeout = setTimeout(func, remaining); } }};
链接:https://www.jianshu.com/p/a084bbe9102c
//节流函数(连续触发会不执行)
// throttle:function (func, wait){
// var timeout,
// context,
// args,
// startTime = Date.parse(new Date());
//
// return function(){
// var curTime = Date.parse(new Date());
// var remaining = wait - (curTime - startTime);
// context = this;
// args = arguments;
//
// clearTimeout(timeout);
//
// if(remaining <= 0){
// func.apply(context, args);
// startTime = Date.parse(new Date());
// }else
// timeout = setTimeout(func, remaining);
// }
// }
// },
//delay的间隔内连续触发的调用,后一个调用会把前一个调用的等待处理掉,但每隔mustRunDelay至少执行一次。第2个版本,其实是防抖
// throttle :function(fn,delay,mustRunDelay){
// var timer=null;
// var t_start;
// return function(){
// var context=this,args=arguments,t_curr=+new Date();
// clearTimeout(timer);
// if(!t_start){
// t_start=t_curr;
// }if(t_curr-t_start>=mustRunDelay){
// fn.apply(context,args);
// t_start=t_curr;
// }else{
// timer=setTimeout(function(){
// fn.apply(context,args);
// },delay);
// }
// }
// },
//防抖
// debounce:function (func, wait, immediate) {
// var timeout;
// return function() {
// var context = this, args = arguments;
// var later = function() {
// timeout = null;
// if (!immediate) func.apply(context, args);
// };
// var callNow = immediate && !timeout;
// clearTimeout(timeout);
// timeout = setTimeout(later, wait);
// if (callNow) func.apply(context, args);
// };
// },