Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入js之分析防抖与节流 #15

Open
FE-Sadhu opened this issue May 4, 2019 · 0 comments
Open

深入js之分析防抖与节流 #15

FE-Sadhu opened this issue May 4, 2019 · 0 comments
Labels
深入js笔记 about javascript

Comments

@FE-Sadhu
Copy link
Owner

FE-Sadhu commented May 4, 2019

防抖

防抖是什么?

对于一定时间段的连续的函数调用,只让其执行一次。

常用于DOM事件的监听回调中:核心是维护一个定时器,事件被触发n秒后再执行回调,如果n秒内又被触发,则重新计时。

什么时候应用防抖

前端开发中会遇到一些频繁的事件触发,如:

  1. window 的 resize、scroll
  2. mousedown、mousemove
  3. keyup、keydown

这些事件触发后都会执行一个回调函数,如果短时间内有很多次事件触发,则浏览器需要短时间内执行很多次回调,如果次数过于频繁,浏览器会反应不过来,就会出现 卡顿 现象,此时就需要防抖/节流。

举个例子:

// 模拟一段ajax请求
    function ajax(context) {
      console.log('ajax request' + context);
    }

    let inputa = document.getElementById('unDebounce');

    inputa.addEventListener('keyup', function(e) {
      ajax(e.target.value);
    })

此处也频繁执行了keyup的回调,因为这个例子很简单,所以浏览器完全反应的过来,但例子复杂的话,浏览器就会有卡顿了。此时我们可以通过 防抖节流 来解决。

跟着underscore的防抖代码学习

核心是维护一个定时器,事件被触发n秒后再执行回调,如果n秒内又被触发,则重新计时。

    // 模拟一段ajax请求
    function ajax(context) {
      console.log('ajax request' + context.target.value);
    }
    
    function debounce(fn, wait) {
      var timeout = null;

      return function() {
        var context = this; // this指向DOM元素
        var args = arguments; // JavaScript 在事件处理函数中会提供事件对象 event 作为参数。

        clearTimeout(timeout);
        timeout = setTimeout(function() {
          fn.apply(context, args)
        }, wait)
      }
    }

    let inputb = document.getElementById('Debounce');

    inputb.addEventListener('keyup', debounce(ajax, 1000))

可以看到,我们加入了防抖以后,当你在频繁的输入(事件频繁被触发)时,并不会发送请求,只有当你在指定间隔内没有输入时,才会执行函数。如果停止输入但是在指定间隔内又输入,会重新触发计时。

完善防抖函数debounce

立即执行

上面的debounce函数已经可以满足很多场景了,但如果有这样一个需求:

我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。

我们可以加个 immediate 参数判断是否是立刻执行,如下:

function debounce(fn, wait, immediate) { // immediate为立即执行一次的开关
      var timeout = null;
      return function() {
        // 一开始立即执行一次fn,若在wait时间内再次触发事件执行回调,callNow 会为 false,不会调用到fn。若在wait时间后再触发事件则会再次立即执行。
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
          var callNow = !timeout;
          timeout = setTimeout(function() {
            timeout = null;
          }, wait);
          if (callNow) fn.apply(context, args);
        } else {
          timeout = setTimeout(function() {
            fn.apply(context, args);
          }, wait)
        }
      }
    }

来看下效果:

返回值

getUserAction 函数可能是有返回值的,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。

function debounce(fn, wait, immediate) {
      var timeout = null;
      var result;
      return function() {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
          var callNow = !timeout;
          timeout = setTimeout(function() {
            timeout = null;
          }, wait);
          if (callNow) result = fn.apply(context, args);
        } else {
          timeout = setTimeout(function() {
            fn.apply(context, args);
          }, wait)
        }
        return result;
      }
    }

手动取消防抖

希望能取消 debounce 函数,比如说 debounce 的时间间隔是 10 秒钟,immediate 为 true,这样的话,我只有等 10 秒后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行了。

其实很简单,我们在函数上挂个cancel方法就行了:

function debounce(func, wait, immediate) {

    var timeout = null, result;

    var debounced = function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    };

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

    return debounced;
}

比如,需要用一个按钮,点击一下就可以取消,就可以这样使用:

var xxx = debounce(fn, 10000, true);
inputb.addEventListener('keyup', xxx));

document.getElementById("button").addEventListener('click', function(){
    xxx.cancel();
})

至此我们就实现了一个 underscore 中的 debounce 函数。

节流

节流是什么?

让一个函数不要执行得太频繁,减少一些过快的调用来节流

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

也常用于在DOM事件的监听回调中:如果你持续触发事件,每隔一段时间,只执行一次事件。

什么时候应用节流

函数节流有哪些应用场景?哪些时候我们需要间隔一定时间触发回调来控制函数调用频率?

  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 搜索联想(keyup)
  • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次。

按上面的例子:

function throttle(fun, delay) {
        let last, deferTimer
        return function (args) {
            let that = this
            let _args = arguments
            let now = +new Date()
            if (last && now < last + delay) { // 在设置的间隔时间内执行。这段代码保证在间隔时间内停止触发时,delay秒后还会执行一次。
                clearTimeout(deferTimer)
                deferTimer = setTimeout(function () {
                    last = now
                    fun.apply(that, _args)
                }, delay)
            }else { // 一开始立即执行
                last = now
                fun.apply(that,_args)
            }
        }
    }

exp2

跟着underscore节流代码学习

见:JavaScript专题之跟着 underscore 学节流

小结

函数节流和函数去抖的核心其实就是限制某一个方法被频繁触发,而一个方法之所以会被频繁触发,大多数情况下是因为 DOM 事件的监听回调,而这也是函数节流以及去抖多数情况下的应用场景。

按照DOM事件的监听回调来说两者区别的话:防抖是只有等事件停止触发后 n 秒才执行函数,节流是持续触发的时候,每 n 秒执行一次函数。

本文完。

希望看到各位技术人对这篇文章有不同的有“证据”,符合逻辑的分析、看法~

文章会第一时间更新在GitHub,觉得写得还不错的,可以点个star支持下作者🍪


参考:

  1. JavaScript专题之跟着 underscore 学节流
  2. JavaScript专题之跟着 underscore 学防抖
  3. JavaScript 函数节流和函数去抖应用场景辨析
  4. 7分钟理解JS的节流、防抖及使用场景
@FE-Sadhu FE-Sadhu added the 深入js笔记 about javascript label May 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
深入js笔记 about javascript
Projects
None yet
Development

No branches or pull requests

1 participant