美文网首页web前端经典面试题
js函数防抖、节流实现

js函数防抖、节流实现

作者: 全栈弄潮儿 | 来源:发表于2019-04-30 11:34 被阅读0次

    React中使用防抖函数和节流函数

    在React事件调用时,React传递给事件处理程序是一个合成事件对象的实例。SyntheticEvent对象是通过合并得到的。 这意味着在事件回调被调用后,SyntheticEvent 对象将被重用并且所有属性都将被取消。 这是出于性能原因。 因此,您无法以异步方式访问该事件。React合成事件官方文档

    所以在用防抖或节流函数封装时,异步方式访问事件对象出现问题。解决的方法如下:

    方法一:调用合成事件对象的persist()方法 event.persist && event.persist() //保留对事件的引用
    方法二:深拷贝事件对象 const event = e && {...e} //深拷贝事件对象

    function debounce(func, wait=500) {
        let timeout;  // 定时器变量
        return function(event){
            clearTimeout(timeout);  // 每次触发时先清除上一次的定时器,然后重新计时
            event.persist && event.persist()   //保留对事件的引用
            //const event = e && {...e}   //深拷贝事件对象
            timeout = setTimeout(()=>{
                func(event)
            }, wait);  // 指定 xx ms 后触发真正想进行的操作 handler
        };
    }
    

    防抖debounce

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

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

    应用场景

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

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

    (2)其他场景

    表单组件输入内容验证
    防止多次点击导致表单多次提交

    ...

    简单实现

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

    react中调用方法

    this.handleGetCustomerNameList = debounce(this.handleGetCustomerNameList.bind(this), 500);
    

    节流 throttle

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

    ### 简单实现
    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;
      };
    };
    

    react中调用方法

    this.handleGetCustomerNameList = throttle (this.handleGetCustomerNameList.bind(this), 500);
    

    参考文章
    JavaScript 函数防抖(debounce)的实现
    React中事件节流防抖
    在React、Vue和小程序中使用函数节流和函数防抖


    更多angular1/2/4/5、ionic1/2/3、react、vue、微信小程序、nodejs等技术文章、视频教程和开源项目,请关注微信公众号——全栈弄潮儿

    微信公众号.png

    脑筋急转弯:

    twister.jpg

    生活小窍门

    qrcode.jpg

    谜语大全:

    riddle.jpg

    相关文章

      网友评论

        本文标题:js函数防抖、节流实现

        本文链接:https://www.haomeiwen.com/subject/agnynqtx.html