9159金沙游艺场


Linux常用命令(一)

PHP的mysqli_query参数MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的区别

javascript中的throttle和debounce浅析

函数防抖与函数节流

2018/06/22 · JavaScript
· 函数

原文出处: 司徒正美   

 

函数防抖与节流是很相似的概念,但它们的应用场景不太一样。

我们先从概念上深刻理解它们。

先说函数防抖,debounce。其概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生
出来的,基本思路就是把多个信号合并为一个信号。

单反也有相似的概念,在拍照的时候手如果拿不稳晃的时候拍照一般手机是拍不出好照片的,因此智能手机是在你按一下时连续拍许多张,
能过合成手段,生成一张。翻译成JS就是,事件内的N个动作会变忽略,只有事件后由程序触发的动作只是有效。

实现思路如下,将目标方法(动作)包装在setTimeout里面,然后这个方法是一个事件的回调函数,如果这个回调一直执行,那么这些动作就一直不执行。为什么不执行呢,我们搞了一个clearTimeout,这样setTimeout里的方法就不会执行!
为什么要clearTimeout呢,我们就需要将事件内的连续动作删掉嘛!待到用户不触发这事件了。那么setTimeout就自然会执行这个方法。

那么这个方法用在什么地方呢,就是用于input输入框架的格式验证,假如只是验证都是字母也罢了,太简单了,不怎么耗性能,如果是验证是否身份证,这性能消耗大,你可以隔170ms才验证一次。这时就需要这个东西。或者你这个是自动完全,需要将已有的输入数据往后端拉一个列表,频繁的交互,后端肯定耗不起,这时也需要这个,如隔350ms。

JavaScript

function debounce(func, delay) { var timeout; return function(e) {
console.log(“清除”,timeout,e.target.value) clearTimeout(timeout); var
context = this, args = arguments console.log(“新的”,timeout,
e.target.value) timeout = setTimeout(function(){ console.log(“—-“)
func.apply(context, args); },delay) }; }; var validate =
debounce(function(e) { console.log(“change”, e.target.value, new Date-0)
}, 380); // 绑定监听
document.querySelector(“input”).addEventListener(‘input’, validate);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function debounce(func, delay) {
    var timeout;
    return function(e) {
        console.log("清除",timeout,e.target.value)
        clearTimeout(timeout);
        var context = this, args = arguments
        console.log("新的",timeout, e.target.value)
        timeout = setTimeout(function(){
          console.log("—-")
          func.apply(context, args);
        },delay)
    };
};
 
var validate = debounce(function(e) {
    console.log("change", e.target.value, new Date-0)
}, 380);
 
// 绑定监听
document.querySelector("input").addEventListener(‘input’, validate);

图片 1

这个保证了正常的用户每输入1,2个字符就能触发一次。如果用户是输入法狂魔,也可以狠制他每输入3~6个字符触发一次。

这个方法的重点是,它在用户不触发事件的时,才触发动作,并且抑制了本来在事件中要执行的动作。

其他应用场合:提交按钮的点击事件。

再看节流,throttle。节流的概念可以想象一下水坝,你建了水坝在河道中,不能让水流动不了,你只能让水流慢些。换言之,你不能让用户的方法都不执行。如果这样干,就是debounce了。为了让用户的方法在某个时间段内只执行一次,我们需要保存上次执行的时间点与定时器。

XHTML

<div id=’panel’ style=”background:red;width:200px;height:200px”>
</div>

1
2
3
<div id=’panel’ style="background:red;width:200px;height:200px">
 
</div>

---

JavaScript

function throttle(fn, threshhold) { var timeout var start = new Date;
var threshhold = threshhold || 160 return function () { var context =
this, args = arguments, curr = new Date() – 0
clearTimeout(timeout)//总是干掉事件回调 if(curr – start >=
threshhold){ console.log(“now”, curr, curr –
start)//注意这里相减的结果,都差不多是160左右 fn.apply(context, args)
//只执行一部分方法,这些方法是在某个时间段内执行一次 start = curr }else{
//让方法在脱离事件后也能执行一次 timeout = setTimeout(function(){
fn.apply(context, args) }, threshhold); } } } var mousemove =
throttle(function(e) { console.log(e.pageX, e.pageY) }); // 绑定监听
document.querySelector(“#panel”).addEventListener(‘mousemove’,
mousemove);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function throttle(fn, threshhold) {
var timeout
var start = new Date;
var threshhold = threshhold || 160
return function () {
 
var context = this, args = arguments, curr = new Date() – 0
clearTimeout(timeout)//总是干掉事件回调
if(curr – start >= threshhold){
     console.log("now", curr, curr – start)//注意这里相减的结果,都差不多是160左右
     fn.apply(context, args) //只执行一部分方法,这些方法是在某个时间段内执行一次
     start = curr
}else{
//让方法在脱离事件后也能执行一次
     timeout = setTimeout(function(){
        fn.apply(context, args)
     }, threshhold);
    }
  }
}
var mousemove = throttle(function(e) {
console.log(e.pageX, e.pageY)
});
 
// 绑定监听
document.querySelector("#panel").addEventListener(‘mousemove’, mousemove);

图片 2

函数节流会用在比input, keyup更频繁触发的事件中,如resize, touchmove,
mousemove, scroll。throttle
会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。

如果还是不能完全体会 debouncethrottle 的差异,可以到
这个页面
看一下两者可视化的比较。

图片 3

2 赞 3 收藏
评论

图片 4

函数节流场景

throttle

例如:实现一个原生的拖拽功能(如果不用H5 Drag和Drop
API),我们就需要一路监听mousemove事件,在回调中获取元素当前位置,然后重置dom的位置。如果我们不加以控制,每移动一定像素而出发的回调数量是会非常惊人的,回调中又伴随着DOM操作,继而引发浏览器的重排和重绘,性能差的浏览器可能会直接假死。这时,我们就需要降低触发回调的频率,比如让它500ms触发一次或者200ms,甚至100ms,这个阀值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能会假死,这时的解决方案就是函数节流【throttle】。函数节流的核心就是:让一个函数不要执行得太频繁,减少一些过快的调用来节流。

我们这里说的throttle就是函数节流的意思。再说的通俗一点就是函数调用的频度控制器,是连续执行时间间隔控制。主要应用的场景比如:

函数去抖场景

1.鼠标移动,mousemove 事件
2.DOM 元素动态定位,window对象的resize和scroll 事件

例如:对于浏览器窗口,每做一次resize操作,发送一个请求,很显然,我们需要监听resize事件,但是和mousemove一样,每缩小(或者放大)一次浏览器,实际上会触发N多次的resize事件,这时的解决方案就是节流【debounce】。函数去抖的核心就是:在一定时间段的连续函数调用,只让其执行一次

有人形象的把上面说的事件形象的比喻成机关枪的扫射,throttle就是机关枪的扳机,你不放扳机,它就一直扫射。我们开发时用的上面这些事件也是一样,你不松开鼠标,它的事件就一直触发。例如:

函数节流的实现

复制代码 代码如下:

函数节流的第一种方案封装如下

var resizeTimer=null;
$(window).on(‘resize’,function(){
       if(resizeTimer){
           clearTimeout(resizeTimer)
       }
       resizeTimer=setTimeout(function(){
           console.log(“window resize”);
       },400);

functionthrottleFunc(method,context){ 
clearTimeout(method.timer);//为什么选择setTimeout
而不是setIntervalmethod.timer = setTimeout(function(){   
method.call(context);  },100);}

debounce

看一个封装的demo

debounce和throttle很像,debounce是空闲时间必须大于或等于
一定值的时候,才会执行调用方法。debounce是空闲时间的间隔控制。比如我们做autocomplete,这时需要我们很好的控制输入文字时调用方法时间间隔。一般时第一个输入的字符马上开始调用,根据一定的时间间隔重复调用执行的方法。对于变态的输入,比如按住某一个建不放的时候特别有用。

window.onscroll =function(){ 
throttleFunc(show);}functionshow(){console.log(1);}functionthrottleFunc(method){ 
clearTimeout(method.timer);  method.timer = setTimeout(function(){   
method();  },100);}

debounce主要应用的场景比如:
文本输入keydown 事件,keyup 事件,例如做autocomplete

也可以使用闭包的方法对上面的函数进行再封装一次

这类网上的方法有很多,比如Underscore.js就对throttle和debounce进行封装。jQuery也有一个throttle和debounce的插件:jQuery
throttle /
debounce,所有的原理时一样的,实现的也是同样的功能。再奉上一个自己一直再用的throttle和debounce控制函数:

functionthrottle(fn, delay){vartimer =null;returnfunction(){   
clearTimeout(timer);    timer = setTimeout(function(){      fn();    },
delay); };};

复制代码 代码如下:

调用

/*
* 频率控制 返回函数连续调用时,fn 执行频率限定为每多少时间执行一次
* @param fn {function}  需要调用的函数
* @param delay  {number}    延迟时间,单位毫秒
* @param immediate  {bool} 给 immediate参数传递false
绑定的函数先执行,而不是delay后后执行。
* @return {function}实际调用函数
*/
var throttle = function (fn,delay, immediate, debounce) {
   var curr = +new Date(),//当前事件
       last_call = 0,
       last_exec = 0,
       timer = null,
       diff, //时间差
       context,//上下文
       args,
       exec = function () {
           last_exec = curr;
           fn.apply(context, args);
       };
   return function () {
       curr= +new Date();
       context = this,
       args = arguments,
       diff = curr – (debounce ? last_call : last_exec) – delay;
       clearTimeout(timer);
       if (debounce) {
           if (immediate) {
               timer = setTimeout(exec, delay);
           } else if (diff >= 0) {
               exec();
           }
       } else {
           if (diff >= 0) {
               exec();
           } else if (immediate) {
               timer = setTimeout(exec, -diff);
           }
       }
       last_call = curr;
   }
};

varfunc =
throttle(show,100);functionshow(){console.log(1);}window.onscroll
=function(){  func();}

/*
* 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 delay,fn
才会执行
* @param fn {function}  要调用的函数
* @param delay   {number}    空闲时间
* @param immediate  {bool} 给 immediate参数传递false
绑定的函数先执行,而不是delay后后执行。
* @return {function}实际调用函数
*/

封装2

var debounce = function (fn, delay, immediate) {
   return throttle(fn, delay, immediate, true);

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);    }  }}

我们这里说的throttle就是函数节流的意思。再说的通俗一点就是函数调用的频度控制器,是连续执行时间间隔控制。主要应用的场景…

调用

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); } }};

链接:

//节流函数(连续触发会不执行)

    // throttle:function (func, wait){

    //    var timeout,

    //        context,

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图