Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • CSS

  • Npm

  • Vue

  • HTML

  • Node

  • Yaml

  • React

  • 框架

  • 规范

  • Electron

  • JS演示

  • VuePress

  • JavaScript

    • DOM
    • 事件
    • 实用API
    • 作用域
    • 基础篇
    • 定时器
    • 内存管理
    • 异步操作
    • 日期处理
    • 点击约束
    • 防抖节流
      • 防抖
      • 节流
      • 异同比较
      • 防抖与节流的应用
        • 函数防抖的应用场景
        • 函数节流的应用场景
    • this的学习
    • ES面向对象
    • JS事件详解
    • new命令原理
    • 内置对象篇
    • 函数柯里化
    • 正则表达式
    • 浏览器模型
    • 事件订阅发布
    • 获取页面高度
    • 面向对象编程
    • JS随机打乱数组
    • js各种开发技巧
    • 函数的深入浅出
    • 防抖与节流函数
    • 模块化的使用总结
    • JS获取和修改url参数
    • JS设计模式总结笔记
    • fetch拦截和封装使用
    • 浏览器渲染原理流程
    • JavaScript对象从浅入深
    • JavaScript高级程序设计
    • Promise使用和实现方法
    • JS的种打断点调试方式
    • js深浅拷贝详解与实现
    • 九种跨域方式实现原理
    • 多种数组去重性能对比
    • 闭包原理以及使用场景
    • 判断是否为移动端浏览器
    • 比typeof运算符更准确的类型判断
    • 搞清事件循环、宏任务、微任务
    • 将一维数组按指定长度转为二维数组
  • TypeScript

  • 微信小程序

  • TypeScript-axios

  • 前端
  • JavaScript
wangmings
2022-07-19
目录

防抖节流

# 防抖节流

函数防抖和函数节流:主要优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。

打个比方,早上去上班,坐公交就是防抖, 坐地铁就是节流;

防抖:坐公交就不一样了,到站司机会等待所以客人都上完车后,确定一段时间内没人了就发车

节流:地铁到站只停留规定的时间,到点就发车

# 防抖

首先写代码之前最重要的事情就是想在脑子里面想这段代码需要实现什么逻辑,下面就是防抖的代码逻辑思路。

你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行!

function debounce(func, waitTime) {
  let timeout;
  return function () {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(func, waitTime);
  }
}
document.querySelector('#app').onmousemove = debounce(function fn(event){
    console.log(this);
    console.log(event);
}, 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13

上面的一小段代码debounce函数就是最原始的防抖代码。

以上代码逻辑就是 判断是否存在定时器,存在就清除定时器,然后重新定义个定时器, 定时器到时间再执行fn函数。

可以看到上面这几行代码就用到了闭包的知识,主要的目的就是为了在函数执行后保留timeout这个变量在局部作用域,不污染全局变量。

想让debounce函数执行完后,函数内的timeout变量仍旧保留,就可以使用闭包把要保存的变量在debounce函数作用域内,其他的语句放到子函数作用域里,并且作为一个函数返回。

有一点也许有小伙伴会有疑惑。为什么这里要返回一个函数呢。其实很好理解,我们可以来看下面的代码

let timeout;
function debounce(func, waitTime) {
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = setTimeout(func, waitTime);
}
document.querySelector('#app').onmousemove = debounce(function fn(event){
    console.log(this);
    console.log(event);
}, 1000);
1
2
3
4
5
6
7
8
9
10
11

我删掉了debounce函数里面的return ,然后为了保留timeout,我把它放到了全局变量,这几行代码看起来和上面的很像,但是你可以直接跑一下这段代码,发现debounce只会执行一次!!!

哈哈哈,其实之所以在debounce函数里面返回一个函数,那是因为onmousemove需要的是未执行的函数,我们的测试代码执行一遍后只会返回undefined ,相当于

document.querySelector('#app').onmousemove = debounce(function fn(event){
    console.log(this);
    console.log(event);
}, 1000);
document.querySelector('#app').onmousemove = undefined;
1
2
3
4
5

当然就没有正确绑定事件了。如果从好理解的角度来写,其实也是可以想下面这样绑定的

var timeout;
function debounce(func, waitTime) {
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = setTimeout(func, waitTime);
}
document.querySelector('#app').onmousemove = function(event) {
  debounce(function(){
      console.log(this);
      console.log(event);
  }, 1000);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

这种写法呢和第一个debounce函数通过闭包返回一个函数一样的, 而且用闭包写法可以避免变量的全局污染。

但是这一版本的代码我们在fn中打印this以及event对象,发现有点不对。

fd

从上图看到,this指向了window, event 为undefined了,这个结果并不是希望的,所以我们需要手动把this以及event对象传递给fn函数。于是乎有了下面第二版的防抖函数。

function debounce(func, waitTime) {
  let timeout;
  return function () {
    let _this = this,
        args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(function () {
      func.apply(_this, args)
    },  waitTime);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

上面代码也就是用了apply函数把this以及event对象传递给fn函数。 对this指向问题还不熟悉的同学,可以去看this详解这篇文章

fd1

上图所示结果就是我们想要的结果了,拿到了相对应事件的元素,和event值了

# 节流

下面让我们继续来看一下节流思想的代码逻辑。

当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样才可以设置下个定时器。

function throttle(func, waitTime) {
    let timeout;
    return function() {
        let _this = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(_this, args)
            }, waitTime)
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 异同比较

相同点:

  • 都可以通过使用 setTimeout 实现。
  • 目的都是,降低回调执行频率。节省计算资源。

不同点:

  • 函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于一段时间内只执行一次。

# 防抖与节流的应用

对于像防抖和节流这种工具性质的函数,我们可以把他们放在公共文件里面,然后在需要的地方直接调用就可以了。

防抖和节流最大的核心用处在于优化代码性能,可以用在很多地方,比如输入框的验证,图片懒加载,各种频繁触发的DOM事件等等。

# 函数防抖的应用场景

连续的事件,只需触发一次回调的场景有

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证等等输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

# 函数节流的应用场景

间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 谷歌搜索框,搜索联想功能
  • 高频点击提交,表单重复提交

参考文献

彻底弄懂函数防抖和函数节流 (opens new window)

JavaScript专题系列-防抖和节流 (opens new window)

编辑 (opens new window)
点击约束
this的学习

← 点击约束 this的学习→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式