学习笔记 学习笔记
🐱 首页
    • 🚅 JAVA
    • 🚆 Python
    • 🧭 VUE
    • 🌐 JavaScript
    • 🗺 CSS
  • 🎃 MySQL
  • 🛶 Redis
  • 🛳 Nginx
  • ⚽ Dokcer
  • 🏓 Elasticsearch
  • 🏙 Windows
  • 🗽 Centos
  • ⚓ Gitlab
  • 🙈 分类
  • 🙉 标签
  • 🙊 归档
    • 👣 随笔
    • 🌹 关于
GitHub (opens new window)

爱做梦的奋斗青年

曾梦想仗剑走天涯,后来bug太多就没去
🐱 首页
    • 🚅 JAVA
    • 🚆 Python
    • 🧭 VUE
    • 🌐 JavaScript
    • 🗺 CSS
  • 🎃 MySQL
  • 🛶 Redis
  • 🛳 Nginx
  • ⚽ Dokcer
  • 🏓 Elasticsearch
  • 🏙 Windows
  • 🗽 Centos
  • ⚓ Gitlab
  • 🙈 分类
  • 🙉 标签
  • 🙊 归档
    • 👣 随笔
    • 🌹 关于
GitHub (opens new window)
  • 后端技术

  • web技术

    • JavaScript技术栈

      • JS站点
      • JavaScript开发技巧
      • 实用的JS工具函数
      • 防抖和节流/限频
        • 名词解释
        • 防抖函数
        • 节流函数
        • 思考
        • 应用场景
          • 防抖应用场景
          • 节流的应用场景
        • 示例
      • canvas效果
      • 表白
    • CSS技术栈

    • VUE技术栈

    • vuepress

  • 数据结构

  • C语言

  • 编程技术
  • web技术
  • JavaScript技术栈
爱做梦的奋斗青年
2020-06-24
目录

防抖和节流/限频

防抖(debounce)和节流/限频(throttle)的作用都是在高频事件中防止函数被多次调用,提高用户体验,是一种性能优化的方案。 区别在于,防抖函数只会在高频事件结束后n毫秒调用一次函数,节流函数会在高频事件触发过程当中每隔n毫秒调用一次函数。

# 名词解释

连续操作:两个操作之间的时间间隔小于设定的阀值,这样子的一连串操作视为连续操作。

防抖:一个连续操作中的处理,只触发一次,从而实现防抖动。

节流/限频:一个连续操作中的处理,按照阀值时间间隔进行触发,从而实现节流。

如图所示,其中delay=4,由于红色操作序列与绿色操作序列之间的时间间隔小于delay,所以这两个序列被视为一个连续操作行为。

  • debounceTail:执行操作在连续操作完成之后,触发;
  • debounceStart:执行操作在连续操作完成之前,触发;
  • throttle:在一个连续操作行为中,每间隔delay的时间触发1次。

结合运行图,可以更好的理解防抖、节流的作用。

# 防抖函数

触发高频事件后一段时间(wait)只会执行一次函数,如果指定时间(wait)内高频事件再次被触发,则重新计算时间。

// 防抖函数 且首次执行
// 采用原理:第一操作触发,连续操作时,最后一次操作打开任务开关(并非执行任务),任务将在下一次操作时触发)
function debounceStart(fn, delay, ctx) {
    let immediate = true 
    let movement = null
    return function() {
        let args = arguments
        // 开关打开时,执行任务
        if (immediate) {
            fn.apply(ctx, args)
            immediate = false
        }
        // 清空上一次操作
        clearTimeout(movement)
        // 任务开关打开
        movement = setTimeout(function() {
            immediate = true
        }, delay)
    }
}

// 防抖 尾部执行
// 采用原理:连续操作时,上次设置的setTimeout被clear掉
function debounceTail(fn, delay, ctx) {
    let movement = null
    return function() {
        let args = arguments
        // 清空上一次操作
        clearTimeout(movement)
        // delay时间之后,任务执行
        movement = setTimeout(function() {
            fn.apply(ctx, args)
        }, delay)
    }
}
// 调用
btn.onclick = debounceStart(function(event) {
    console.log('100ms')
}, 100, this) 
btn.onclick = debounceTail(function(event) {
    console.log('100ms')
}, 100, this) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 节流函数

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

// 节流函数
function throttle(func,delay,context) {
    let timeout = null;
    return function () {
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, delay)
        }
    }
}
// 调用
window.onscroll = throttle(function(event) {
    console.log('100ms')
}, 100, this) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

通过控制台可以看到,不进行限频时,scroll在1s内可以触发高达上100次,增加了限频之后,就将scroll的触发控制在一定的范围内。

# 思考

在实际的使用场景当中,我们会发现,用户最后一次操作并没有后续的处理,也就是最后一次操作的状态将丢失。在某些应用场景当中,可能造成状态处理不准确。如通过scroll事件判断是否到达页面底部,如果到达,则提示用户。使用throttle方法进行节流,在到达底部之前,小于delay的时间间隔内,触发了一次位置判断操作;下一次触发将在delay时间之后,但在那之前,scroll事件已经结束了,所以无法获取最后scroll到底部的位置,也就不会触发提示。

如何优化呢?可以结合debounceTail的功能,其可以实现最后一次操作的捕捉,如图所示:

增加movement来记录和清除最终操作状态;用count来避免与限频的重合;如此便实现了捕获最终操作状态的限频操作。

// 限频,每delay的时间执行一次
function throttle(fn, delay, ctx) {
    let isAvail = true
    let count = false
    let movement = null
    return function() {
        count = true
        let args = arguments
        if (isAvail) {
            fn.apply(ctx, args)
            isAvail = false
            count = false
            setTimeout(function() {
                isAvail = true
            }, delay)
        }
        if (count) {
            clearTimeout(movement)
            movement = setTimeout(function() {
                fn.apply(ctx, args)
            }, 2 * delay)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 应用场景

常见的应用场景都是使用高频事件来调用函数的过程当中,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。

# 防抖应用场景

  • scroll事件滚动触发事件
  • 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
  • 表单验证
  • 按钮提交事件。
  • 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等。

# 节流的应用场景

  • DOM 元素的拖拽功能实现(mousemove)
  • 搜索联想(keyup)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 监听滚动事件判断是否到页面底部自动加载更多

# 示例

See the Pen 防抖与节流函数 by xugaoyi (@xugaoyi) on CodePen.

编辑此页 (opens new window)
#js#性能提升
上次更新: 2021/08/11 01:55:40
实用的JS工具函数
canvas效果

← 实用的JS工具函数 canvas效果→

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式