fe-interview icon indicating copy to clipboard operation
fe-interview copied to clipboard

什么是函数节流和函数防抖?应用场景是怎么样的?

Open FunnyZ opened this issue 6 years ago • 1 comments

什么是函数节流和函数防抖?各自的应用场景是怎么样的?

FunnyZ avatar Jul 18 '19 16:07 FunnyZ

防抖debounce

防抖(Debounce): 多次触发,只在最后一次触发时,执行目标函数。

函数防抖就是,延迟一段时间再执行函数,如果这段时间内又触发了该函数,则延迟重新计算。

应用场景

(1)通过监听某些事件完成对应的需求,比如:

  • 通过监听 scroll 事件,检测滚动位置,根据滚动位置显示返回顶部按钮
  • 通过监听 resize 事件,对某些自适应页面调整DOM的渲染(通过CSS实现的自适应不再此范围内)
  • 通过监听 keyup 事件,监听文字输入并调用接口进行模糊匹配

(2)其他场景

  • 表单组件输入内容验证
  • 防止多次点击导致表单多次提交
  • search模糊搜索,用户在不断输入值时,用防抖来节约请求资源 ......

简单实现

function debounce(fn, wait) {
  let t;
  return () => {
    let context = this;
    let args = arguments;
    if (t) clearTimeout(t);
    t= setTimeout(() => {
        fn.apply(context, args);
    }, wait)
  }
}

完整实现

function debounce(func, wait, immediate) {
    let time;
    let debounced = function() {
        let context = this;
        if(time) clearTimeout(time);

        if(immediate) {
            let callNow = !time;
            if(callNow) func.apply(context, arguments);
            time = setTimeout(
                ()=>{time = null} //见注解
            , wait)
        } else {
            time = setTimeout(
                ()=>{func.apply(context, arguments)}
            , wait) 
        }
    };

    debounced.cancel = function() {
        clearTimeout(time);
        time = null;
    };
    return debounced;
}

// underscore.js debounce

//
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.

_.debounce = function(func, wait, immediate) {
  var timeout, args, context, timestamp, result;

  // 处理时间
  var later = function() {
    var last = _.now() - timestamp;

    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last); // 10ms 6ms 4ms
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

节流 throttle

节流(Throttle):函数间隔一段时间后才能再触发,避免某些函数触发频率过高,比如滚动条滚动事件触发的函数。

应用场景

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多
### 简单实现
function throttle (fn, wait, mustRun) {
  let start = new Date()
  let timeout
  return () => {
    // 在返回的函数内部保留上下文和参数
    let context = this;
    let args = arguments;
    let current = new Date();

    clearTimeout(timeout);

    let remaining = current - start;
    // 达到了指定触发时间,触发该函数
    if (remaining > mustRun) {
      fn.apply(context, args);
      start = current;
    } else {
      // 否则wait时间后触发,闭包保留一个timeout实例
      timeout = setTimeout(fn, wait);
    }
  }
}

完整实现

function throttle(func, wait, options) {
    let time, context, args, result;
    let previous = 0;
    if (!options) options = {};

    let later = function () {
        previous = options.leading === false ? 0 : new Date().getTime();
        time = null;
        func.apply(context, args);
        if (!time) context = args = null;
    };

    let throttled = function () {
        let now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        let remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (time) {
                clearTimeout(time);
                time = null;
            }
            previous = now;
            func.apply(context, args);
            if (!time) context = args = null;
        } else if (!time && options.trailing !== false) {
            time = setTimeout(later, remaining);
        }
    };
    return throttled;
}

// underscore.js throttle

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.

_.throttle = function(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = _.now();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    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) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

详情见我的简书-js防抖函数、节流函数实现, 以及在react项目中的使用

artdong avatar Jul 19 '19 01:07 artdong