Fork me on GitHub

函数防抖与节流

在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

函数防抖(debounce)

函数防抖(debounce):
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。

在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:
如果短时间内大量触发同一事件,只会执行一次函数。

实现:
函数防抖的基本思想是设置一个定时器,在指定时间间隔内运行代码时清楚上一次的定时器,并设置另一个定时器,知道函数请求停止并超过时间间隔才会执行。,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。

使用场景:
文本框输入搜索(连续输入时避免多次请求接口)

防抖debounce代码:

    /**
     * 防抖函数
     * @param {Fcuntion} fn 要执行的函数
     * @param {Number} delay 延迟执行的毫秒数,默认是200毫秒
     * @return {Function}    防抖函数
     */
    function debounce(fn, delay) {
      var timer = null;    //借助闭包
      var delay = delay || 200;
      return function() {
        var args = arguments;
        var that = this;
        // 清楚上一次的定时器
        if(timer){
            clearTimeout(timer)
        }
        timer = setTimeout(function() {
            fn.apply(that,args);
        }, delay);
      }
    }

	
// 处理函数
function handle(str){
	console.log('write---',str,Date.now());
}
// 监听input输入change事件
// 注意:如果防抖的函数不需要参数的话,可以使用第一种写法防抖,如果需要防抖的函数还需要传入参数,请使用第二种写法。
//防抖第一种写法:
$('input').on('input',debounce(handle,2000));

//防抖第二种写法:
var debounced = debounce(handle,2000);
$('input').on('input',function(){
	debounced($(this).val());
});


防抖函数(来自uView2.0源码):

let timeout = null

/**
 * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
 *
 * @param {Function} func 要执行的回调函数
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 */
function debounce(func, wait = 500, immediate = false) {
    // 清除定时器
    if (timeout !== null) clearTimeout(timeout)
    // 立即执行,此类情况一般用不到
    if (immediate) {
        const callNow = !timeout
        timeout = setTimeout(() => {
            timeout = null
        }, wait)
        if (callNow) typeof func === 'function' && func()
    } else {
        // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
        timeout = setTimeout(() => {
            typeof func === 'function' && func()
        }, wait)
    }
}

export default debounce

当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。

函数节流(throttle)

函数节流(throttle):
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。规定在一个单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效; 典型的案例就是鼠标不断点击触发,规定在n秒内多次点击只有一次生效。

效果:
如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

实现原理:
其原理是用时间戳来判断是否已到回调该执行时间,记录上次执行的时间戳,然后每次触发 scroll 事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经到达 规定时间段,如果是,则执行,并更新上次执行的时间戳,

使用场景
resize、scroll、mousemove等事件触发监听

节流throttle代码:

    /**
     * 配置节流函数
     * @param  {Function}  fn   要执行的函数
     * @param  {Number}  delay  延迟执行的毫秒数,默认是200毫秒
     * @return {Function}       节流函数
     */
    function throttle(fn, delay) {
      // 上一次函数触发时间
      var lastTime;
      var timer = null;
      var delay = delay || 200;
      return function() {
        // 记录当前函数触发的时间
        var nowTime = Date.now();
        var context = this;
        var args = arguments;
        if (lastTime && nowTime - lastTime < delay) {
          // 如果上一次函数触发时间存在,并且现在触发时间与上一次触发时间差值小于delay
          // 说明触发时间太短,还没有到达规定的delay时间
          // 所以先清除上一次的定时器,重新设置延迟执行
          clearTimeout(timer);
          timer = setTimeout(function(){
            // 记录上一次函数触发的时间
            lastTime = nowTime;
            fn.apply(context, args);
          }, delay);
        } else {
          lastTime = nowTime;
          fn.apply(context, args);
        }
      }
    }
	

// 处理函数
function handle(id){
     console.log('aa---',id,Date.now());
}
// 监听window的scroll事件
// 注意:如果节流的函数不需要参数的话,可以使用第一种写法节流,如果需要节流的函数还需要传入参数,请使用第二种写法。
//节流第一种写法:
$(window).on('scroll',throttle(handle, 3000));

//节流第二种写法:
var throttled = throttle(aa,2000);
$(window).on('scroll', function(){
	throttled(99)
});


节流函数(来自uView2.0源码):

let timer; let
    flag
/**
 * 节流原理:在一定时间内,只能触发一次
 *
 * @param {Function} func 要执行的回调函数
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 */
function throttle(func, wait = 500, immediate = true) {
    if (immediate) {
        if (!flag) {
            flag = true
            // 如果是立即执行,则在wait毫秒内开始时执行
            typeof func === 'function' && func()
            timer = setTimeout(() => {
                flag = false
            }, wait)
        }
    } else if (!flag) {
        flag = true
        // 如果是非立即执行,则在wait毫秒内的结束处执行
        timer = setTimeout(() => {
            flag = false
            typeof func === 'function' && func()
        }, wait)
    }
}
export default throttle

总结

函数防抖(debounce):将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

函数节流(throttle):使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

posted @ 2019-07-16 21:28  较瘦  阅读(223)  评论(0编辑  收藏  举报
知识点文章整理