事件的节流(throttle)与防抖(debounce)

上一节我们一起通过监听滚动事件,实现了各大网站喜闻乐见的懒加载效果。但我们提到,scroll 事件是一个非常容易被反复触发的事件。其实不止 scroll 事件,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)都存在被频繁触发的风险。

频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。就是在这样的背景下,throttle(事件节流)和 debounce(事件防抖)出现了。

“节流”与“防抖”的本质

这两个东西都以闭包的形式存在。

它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。

Throttle: 第一个人说了算

throttle 的中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。

先给大家讲个小故事:现在有一个旅客刚下了飞机,需要用车,于是打电话叫了该机场唯一的一辆机场大巴来接。司机开到机场,心想来都来了,多接几个人一起走吧,这样这趟才跑得值——我等个十分钟看看。于是司机一边打开了计时器,一边招呼后面的客人陆陆续续上车。在这十分钟内,后面下飞机的乘客都只能乘这一辆大巴,十分钟过去后,不管后面还有多少没挤上车的乘客,这班车都必须发走。

在这个故事里,“司机” 就是我们的节流阀,他控制发车的时机;“乘客”就是因为我们频繁操作事件而不断涌入的回调任务,它需要接受“司机”的安排;而“计时器”,就是我们上文提到的以自由变量形式存在的时间信息,它是“司机”决定发车的依据;最后“发车”这个动作,就对应到回调函数的执行。

总结下来,所谓的“节流”,是通过在一段时间内无视后来产生的回调请求来实现的。只要一位客人叫了车,司机就会为他开启计时器,一定的时间内,后面需要乘车的客人都得排队上这一辆车,谁也无法叫到更多的车。

对应到实际的交互上是一样一样的:每当用户触发了一次 scroll 事件,我们就为这个触发操作开启计时器。一段时间内,后续所有的 scroll 事件都会被当作“一辆车的乘客”——它们无法触发新的 scroll 回调。直到“一段时间”到了,第一次触发的 scroll 事件对应的回调才会执行,而“一段时间内”触发的后续的 scroll 回调都会被节流阀无视掉。

理解了大致的思路,我们现在一起实现一个 throttle:

// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {// last为上一次触发回调的时间let last = 0// 将throttle处理结果当作函数返回return function () {// 保留调用时的this上下文let context = this// 保留调用时传入的参数let args = arguments// 记录本次触发回调的时间let now = +new Date()// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值if (now - last >= interval) {// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调last = now;fn.apply(context, args);}}
}// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)document.addEventListener('scroll', better_scroll)

Debounce: 最后一个人说了算

防抖的中心思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。

继续讲司机开车的故事。这次的司机比较有耐心。第一个乘客上车后,司机开始计时(比如说十分钟)。十分钟之内,如果又上来了一个乘客,司机会把计时器清零,重新开始等另一个十分钟(延迟了等待)。直到有这么一位乘客,从他上车开始,后续十分钟都没有新乘客上车,司机会认为确实没有人需要搭这趟车了,才会把车开走。

我们对比 throttle 来理解 debounce:在throttle的逻辑里,“第一个人说了算”,它只为第一个乘客计时,时间到了就执行回调。而 debounce 认为,“最后一个人说了算”,debounce 会为每一个新乘客设定新的定时器。

我们基于上面的理解,一起来写一个 debounce:

// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {// 定时器let timer = null// 将debounce处理结果当作函数返回return function () {// 保留调用时的this上下文let context = this// 保留调用时传入的参数let args = arguments// 每次事件被触发时,都去清除之前的旧定时器if(timer) {clearTimeout(timer)}// 设立新定时器timer = setTimeout(function () {fn.apply(context, args)}, delay)}
}// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000)document.addEventListener('scroll', better_scroll)

用 Throttle 来优化 Debounce

debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。

为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttle 与 debounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中:

// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {// last为上一次触发回调的时间, timer是定时器let last = 0, timer = null// 将throttle处理结果当作函数返回return function () { // 保留调用时的this上下文let context = this// 保留调用时传入的参数let args = arguments// 记录本次触发回调的时间let now = +new Date()// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值if (now - last < delay) {// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器clearTimeout(timer)timer = setTimeout(function () {last = nowfn.apply(context, args)}, delay)} else {// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应last = nowfn.apply(context, args)}}
}// 用新的throttle包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)document.addEventListener('scroll', better_scroll)

vue 使用防抖函数

新建utils.js

export function debounce(fn, delay, immdiate = false, resultCallback) {let timer = nulllet isInvoke = falsefunction _debounce(...arg) {if (timer) clearTimeout(timer)if (immdiate && !isInvoke) {const result = fn.apply(this, arg)if (resultCallback && typeof resultCallback === "function") resultCallback(result)isInvoke = true} else {timer = setTimeout(() => {const result = fn.apply(this, arg)if (resultCallback && typeof resultCallback === "function") resultCallback(result)isInvoke = falsetimer = null}, delay)}}_debounce.cancel = function () {if (timer) clearTimeout(timer)timer = nullisInvoke = false}return _debounce
}

使用

<template><div><div class="Immediately Immediately-anima" @click="ImmediatelyClick"></div></div>
</template>
<script>
import { debounce} from '@/utils';
export default {data() {return {};},methods: {ImmediatelyClick:debounce(function() {console.log('您已领取过了')}, 500,false)}
};
</script>
<style scoped></style>

小结

throttle 和 debounce 不仅是我们日常开发中的常用优质代码片段,更是前端面试中不可不知的高频考点。“看懂了代码”、“理解了过程”在本节都是不够的,重要的是把它写到自己的项目里去,亲自体验一把节流和防抖带来的性能提升。

性能优化 之 节流(throttle)与防抖(debounce) vue 使用相关推荐

  1. 事件触发控制_前端性能优化:事件的节流throttle与防抖debounce

    scroll 事件是一个非常容易被反复触发的事件,另外,resize 事件.鼠标事件(比如 mousemove.mouseover 等).键盘事件(keyup.keydown 等)都存在被频繁触发的风 ...

  2. 节流(Throttle)和防抖(Debounce)

    为啥要使用防抖和节流 在项目开发中,我们经常用到的滚动事件或者用户输入事件,都是一些高频事件,如果对这类事件触发的频率没有节制,就会加重浏览器和服务器的负担.节流和防抖目的就是减少事件触发的次数. 1 ...

  3. 【前端帮帮忙】第7期 关于节流(throttle)和防抖(debounce)的理解

    节流和防抖在我们平时的项目中挺常用的,也是面试中经常会被提问的知识点,今天我们一起来学习一下. 节流 简单理解就是:控制函数每隔n秒执行一次. 作用 防止用户高频率的触发事件,刚好这个事件又需要处理大 ...

  4. JavaScript 函数节流 throttle 和防抖 debounce

    今天和别人聊到JavaScript函数的节流和防抖,发现自己对这两个的区别很是模糊,遂小小实践一下,在此记录,希望对需要的人有所帮助. 节流 - 频繁操作,间隔一定时间去做一件事 举例说明:假定时间间 ...

  5. 性能优化之节流(throttling)与防抖(debounce)

    前言 首先来举个例子.百度首页的百度输入框,用户输入的时候,每次输入的信息,我们都能看到百度服务器返回给我们的联想关键字.我们每改动一个字,它就换一次联想词,这是我们肉眼能看到的速度,实际上如果不加以 ...

  6. 性能优化之节流、防抖

    1. 防抖: 由于dom操作极其昂贵,所以尝试过多的dom操作有可能会将浏览器搞崩溃,比如onresize.onscroll这类事件操作: 为了解决这个问题,引出防抖的概念(某些代码不可以在没有间断的 ...

  7. 【前端性能优化】不用 setTimeout 实现防抖

    相信大家都会使用 setTimeout 来实现防抖操作,但是如果有特殊情况,不能使用 setTimeout 怎么办呢,那么就可以使用以下方法来实现 /*** 防止多次点击按钮*/ function t ...

  8. 前端性能优化之防抖-debounce

    这周接到一个需求-给输入框做模糊匹配.这还不简单,监听input事件,取到输入值去调接口不就行了? 然而后端小哥说不行,这个接口的数据量非常大,这种方式调用接口的频率太高,而且用户输入时调用根本没有必 ...

  9. 关于前端性能优化问题,认识网页加载过程和防抖节流

    前端性能优化-网页加载过程.性能优化方法.防抖和节流 一.网页加载过程 1.加载资源的形式 2.加载资源的过程 3.渲染页面的过程 4.关于window.onload 和 DOMContentLoad ...

  10. JS高级 之 防抖 debounce - throttle 节流

    目录 一.防抖 debounce 1. 概念 2. 应用场景 3. 使用 underscore 实现防抖 01 - 代码 02 - 效果 4. 实现 01 - 基本实现 代码 效果 02 - 优化 = ...

最新文章

  1. Oracle Instanc Client安装命令工具
  2. 禁止Win7系统自动安装驱动程序
  3. android开发中,可能会导致内存泄露的问题
  4. Vue+Openlayers+Draw实现画笔切换功能,切换画笔为点、线、面
  5. 【Pytorch神经网络理论篇】 13 深层卷积神经网络介绍+池化操作+深层卷积神经网络实战
  6. SpringMVC启动过程详解(li)
  7. 【转载】UltraWinGrid使用心得(C#)
  8. 那年,三支玫瑰的花语是我们每人一支
  9. oracle判断一个值不在记录中,Oracle: DELETE前不需SELECT判断记录是否存在,INSERT前不需SELECT判断是否有若干字段值重复的记录。...
  10. 基于springboot的多人聊天系统
  11. matlab 代码 经典例题,MATLAB程序设计及经典例题解析3
  12. [TravelNotes] CTSC 2017 APIO 2017 THUPC 2017 游记
  13. L2D1 linux下deb包管理及常用shell命令
  14. vm虚拟机安装以及镜像和网路配置
  15. mysql操作语句类型DQL\DML\DDL\DCL
  16. 【附源码】计算机毕业设计SSM网上商城比价系统
  17. 50个超酷的Photoshop的渐变画笔
  18. RobotFramework实现键盘组合按键
  19. 张一鸣当班主任 10万字节人到底多少在摸鱼
  20. 给教师的计算机配置,老师每人配备一台电脑,有无必要?资深教师告诉你!

热门文章

  1. 挂一张表,省的再瞎眼
  2. 怎么看神经网络过早收敛_深度学习训练网络中,test loss比training loss早很多收敛,迭代次数要怎么取?...
  3. php getimagesize 导致系统变慢
  4. 【起航计划ObjC 003】印第安老斑鸠ObjC的幻想 ---- ObjC经典问题
  5. 裸眼 3D 技术是什么原理?
  6. FMI飞马网线上直播-《ELK Stack深入浅出》
  7. 文本相似(汉明距离)
  8. 怎么把视频复制到IPAD
  9. 程序员制作epub电子书
  10. 新天龙官网服务器更新消息,《新天龙八部》1月20日全服更新维护公告