防抖(debounce) 

什么是防抖函数:

防抖函数在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。

应用场景:

(1) 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源;

(2) window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次;

实现过程:

代码说明:


    开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 null ,就可以再次点击了

    对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间 隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间 减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数


*
 * @param {function} func 回调函数
 * @param {number} wait 表示时间窗口的间隔
 * @param {boolean} immediate 设置为ture时,是否立即调用函数
 * @return {function} 返回客户调用函数
 */
_.debounce = function(func, wait, immediate) {
 var timeout, args, context, timestamp, result;
 var later = function() {
 // 现在和上一次时间戳比较
 var last = _.now() - timestamp;
 // 如果当前间隔时间少于设定时间且大于0就重新设置定时器
 if (last < wait && last >= 0) {
 timeout = setTimeout(later, wait - last);
 } else {
 // 否则的话就是时间到了执行回调函数
 timeout = null;
 if (!immediate) {
 result = func.apply(context, args);
 if (!timeout) context = args = null;
 }
 }
 };
 return function() {
 context = this;
 args = arguments;
 // 获得时间戳
 timestamp = _.now();
 // 如果定时器不存在且立即执行函数
 var callNow = immediate && !timeout;
 // 如果定时器不存在就创建一个
 if (!timeout) timeout = setTimeout(later, wait);
 if (callNow) {
 // 如果需要立即执行函数的话 通过 apply 执行
 result = func.apply(context, args);
 context = args = null;
 }
 return result;
 };
 };

节流(throttling)

什么是节流函数:


防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行


应用场景:

(1)鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;

(2)在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;

(3)监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;

实现过程:

代码说明:

/**
 * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
 *
 * @param {function} func 回调函数
 * @param {number} wait 表示时间窗口的间隔
 * @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
 * 如果想忽略结尾函数的调用,传入{trailing: false}
 * 两者不能共存,否则函数不能执行
 * @return {function} 返回客户调用函数 
 */
_.throttle = function(func, wait, options) {
 var context, args, result;
 var timeout = null;
 // 之前的时间戳
 var previous = 0;
 // 如果 options 没传则设为空对象
 if (!options) options = {};
 // 定时器回调函数
 var later = function() {
 // 如果设置了 leading,就将 previous 设为 0
 // 用于下面函数的第一个 if 判断
 previous = options.leading === false ? 0 : _.now();
 // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
 timeout = null;
 result = func.apply(context, args);
 if (!timeout) context = args = null;
 };
 return function() {
 // 获得当前时间戳var now = _.now();
 // 首次进入前者肯定为 true
 // 如果需要第一次不执行函数
 // 就将上次时间戳设为当前的
 // 这样在接下来计算 remaining 的值时会大于0
 if (!previous && options.leading === false) previous = now;
 // 计算剩余时间
 var remaining = wait - (now - previous);
 context = this;
 args = arguments;
 // 如果当前调用已经大于上次调用时间 + wait
 // 或者用户手动调了时间
 // 如果设置了 trailing,只会进入这个条件
 // 如果没有设置 leading,那么第一次会进入这个条件
 // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
 // 其实还是会进入的,因为定时器的延时
 // 并不是准确的时间,很可能你设置了2秒
 // 但是他需要2.2秒才触发,这时候就会进入这个条件
 if (remaining <= 0 || remaining > wait) {
 // 如果存在定时器就清理掉否则会调用二次回调
 if (timeout) {
 clearTimeout(timeout);
 timeout = null;
 }
 previous = now;
 result = func.apply(context, args);
 if (!timeout) context = args = null;
 } else if (!timeout && options.trailing !== false) {
 // 判断是否设置了定时器和 trailing
 // 没有的话就开启一个定时器
 // 并且不能不能同时设置 leading 和 trailing
 timeout = setTimeout(later, remaining);
 }
 return result;
 };
 };

如果事件触发是高频但是有停顿时,可以选择debounce; 在事件连续不断高频触发时,只能选择throttling,因为debounce可能会导致动作只被执行一次,界面出现跳跃。