JavaScript防抖与节流

概念

防抖(debounce)与节流(throttle)是两个相似但有本质区别的两个概念,但两个概念的存在都是为了控制在特定条件下函数最大的执行次数。这在例如将函数执行onScroll事件绑定这类事件发生次数过多导致回调函数在任务队列积压、回调函数执行时间过长导致调用栈阻塞容易造成前端性能瓶颈时尤为重要,onScroll事件在拖动滚动条或在手机页面滑动时会发生30-100次,此时如果回调函数内有略微影响性能的函数执行,这个效果会被放大很多倍。这时防抖与节流处理显得尤为重要。

防抖 Debounce

防抖(Debounce)是将某段时间或某两个特定事件之间的多个调用合为一次调用的操作,如果在规定时间内没有调用,则将前面的多个连续调用合为一个执行。

每个竖线分割的为时间单元,着色为在该时间单元内有事件发生,上方是事件处理前的发生情况,下方是防抖处理后的情况。可以看到防抖处理在事件停止连续执行某一时间段后将发生过的连续多个事件合成为一个事件。

前缘防抖 Leading Edge

前缘防抖(Leading Edge 或 Immediate),是将防抖事件的发生放在事件开始时的一种改良。事件发生时前缘防抖立刻放出一个对应事件,后续连续放生的事件将被前缘防抖过滤,直到事件发生间隔大于设定时间后事件再次发生,节流函数将按照相同方式处理事件。

可以看到前缘防抖会在事件发生时先立刻执行,之后将进行前缘防抖操作。

Lodash的防抖

JavaScript库Lodash包含_.debounce(function, [wait=0], [options={}])函数可以实现防抖function为需要防抖的函数,wait是可选的延迟毫秒数,option.leading标记是(true)否(false)使用前缘防抖这个值默认为falseoption.trailing标记是否使用默认防抖这个值默认为trueoptions.maxWait标记函数最大延迟时间。

_.debounce(sendEmail, 400, {
leading: true,
trailing: false,
maxWait: 1000
})

实际使用的例子

// 避免窗口在变动时出现昂贵的计算开销。
$(window).on('resize', _.debounce(calculateLayout, 150)); // 当点击时 `sendMail` 随后就被调用。
$(element).on('click', _.debounce(sendMail, 300, {
'leading': true,
'trailing': false
})); // 确保 `batchLog` 调用1次之后,1秒内会被触发。
var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
var source = new EventSource('/stream');
$(source).on('message', debounced); // 取消一个 trailing 的防抖动调用
$(window).on('popstate', debounced.cancel); // 左div会在每次浏览器发出resize时打印,右div会在400ms内无resize事件发出时打印
$(document).ready(function(){
var $win = $(window);
var $left_panel = $('.left-panel');
var $right_panel = $('.right-panel'); function display_info($div) {
$div.append($win.width() + ' x ' + $win.height() + '<br>');
} $(window).on('resize', function(){
display_info($left_panel);
}); $(window).on('resize', _.debounce(function() {
display_info($right_panel);
}, 400));
});

仅安装Lodash的debounce和throttle功能的方法

npm i -g lodash-cli
lodash include = debounce, throttle

防抖的实现

自行实现防抖与节流其实并不难,只要利用好setTimeout就可以了

function debounce(method, arguments, ctx, time) {
if (typeof method.tId === "undefined") {
method.tId = 0;
method.call(ctx, ...arguments);
}
if (method.tId) {
clearTimeout(method.tId);
}
method.tId = setTimeout(() => {
method.tId = undefined;
}, time || 500);
}

节流 Throttle

节流(Throttle)的目的是在某段时间内允许某个方法执行一次,这与防抖的间隔指定之间分割连续调用段并在这一段的前或后执行一次方法不同,效果就是节流会保证在某段时间内函数将得到执行机会,而防抖则会将只要是按规定时间连续的不论多久或多少次都会将某方法防抖为一次执行。

简单来说就是,节流是根据距离上次执行被节流函数间隔的时间对调用进行过滤,防抖是根据距离上次调用的时间对调用进行过滤。

节流的实现

首先是前缘节流(不知道有这种说法没)。每一次合理的点击(距离上次执行节流后函数的时间大于time或500ms)都将立即执行,否则不执行

/**
* 将一个函数调用包装为节流调用
* @param {function} method 被包装的方法
* @param {array} arguments 调用方法传入的参数
* @param {object} ctx 上下文
* @param {number} time 节流的限制时间
**/
function throttling(method, arguments, ctx, time) {
if (typeof method.tId === "undefined") { // 节流标记位
method.tId = 0;
method.call(ctx, ...arguments);
method.tId = setTimeout(() => {
method.tId = undefined;
}, time || 500);
}
}

再是后缘节流。第一次点击节流后函数将被立即执行,但后续操作只会每time或500ms之后执行一次,也就是后续操作即使在合理频率下也会有延迟

function throttling(method, arguments, ctx,  time) {
if (typeof method.tId === "undefined") { // 节流标记位
method.tId = 0;
method.call(ctx, ...arguments);
return;
}
let tId = method.tId;
if (!tId) {
method.tId = setTimeout(() => {
method.call(ctx, ...arguments);
method.tId = 0;
}, time || 500);
}
}

顺便提一下上述两个函数的使用方法。下方是一个代码片段, 实现当页面按钮点击时在上方获取到的ul元素上添加一个li元素,li元素内部标记有当前addUlElement函数触发时按钮的点击的次数。可以看到我们使用throttling()对点击事件进行了包装,当click事件触发时我们不直接调用addUlElement()函数本体,而是将调用使用节流方法过滤频率过高的调用,然后以合适的频率执行函数。

function addUlElement(num) {
var li = document.createElement("li");
li.innerHTML = "这是第 " + num + " 次点击按钮产生的li";
ul.appendChild(li);
} button.addEventListener("click", () => {
throttling(addUlElement, [++num], this, 1000);
});

rAF requestAnimationFrame

实现执行速率限制的另一种方法是使用requestAnimationFrame,它为60fps的浏览器界面渲染的每帧绑定,它的效果与_.throttle(function, 16)效果差不多,但是可靠性和性能都更好,因为它是微任务且是浏览器原生API。在滚动、鼠标、键盘事件配合调整元素位置或大小、动画时使用该函数是一个优先选择。

rAF的缺点主要来源于IE9、Opera Mini和一些旧安卓浏览器没有该函数支持,Nodejs也不支持该函数

下面是一个使用rAF的实例,我们使用rAF实现每一帧执行一次动画,如果使用循环则会阻塞且执行结果与速度将不可预期,如果使用setTimeoutsetInterval又没有办法精准控制执行次数导致动画并不细腻流畅且使用这两个函数执行动画性能较低。

function step(timestamp) {
if (start === undefined)
start = timestamp;
const elapsed = timestamp - start;
//这里使用`Math.min()`确保元素刚好停在200px的位置。
element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';
if (elapsed < 2000) { // 在两秒后停止动画
window.requestAnimationFrame(step);
}
} window.requestAnimationFrame(step);

总结

  • 防抖 debounce:将一段连续多次的调用合成为一个,默认之后执行,前缘防抖将立即执行
  • 节流 throttle:保证每段时间内至少执行一次调用,可以用于检查、用户操作等
  • rAF:一个节流的更高性能的替代品,最好用于UI相关任务,但不支持IE9

JavaScript防抖与节流笔记的更多相关文章

  1. JavaScript 防抖和节流

    1. 概述 1.1 说明 在项目过程中,经常会遇到一个按钮被多次点击并且多次调用对应处理函数的问题,而往往我们只需去调用一次处理函数即可.有时也会遇到需要在某一规则内有规律的去触发对应的处理函数,所以 ...

  2. 来聊聊JavaScript中的防抖和节流

    目录 JavaScript防抖和节流 问题还原 防抖 什么是防抖 使用场景 节流 什么是节流 使用场景 JavaScript防抖和节流 问题还原 我们先来通过代码把常见的问题还原: <html& ...

  3. 原生JavaScript实现函数的防抖和节流

    原生JavaScript实现函数的防抖和节流 参考:https://www.jianshu.com/p/c8b86b09daf0 想详细了解的直接戳上面链接了,讲得非常清楚.下面只给代码和我自己写的注 ...

  4. JavaScript:防抖与节流

    ①防抖: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <titl ...

  5. 彻底搞懂JavaScript的闭包、防抖跟节流

    最近出去面试了一下,收获颇多!!! 以前的我,追求实际,比较追求实用价值,然而最近面试,传说中的面试造火箭,工作拧螺丝,竟然被我遇到了.虽然很多知识点在实际工作中并不经常用到,但人家就是靠这个来筛选人 ...

  6. JavaScript 中的防抖和节流

    什么是防抖 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时.如下图,持续触发 scrol ...

  7. javascript之防抖与节流

    防抖 你是否在日常开发中遇到一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作. 这些需求都可以通过函数防抖动来实现.尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导 ...

  8. JS的防抖与节流学习笔记

    防抖(debounce):当持续触发事件时,在一定的时间段内,只有最后一次触发的事件才会执行. 例: function debounce(fn, wait) { var timer = null; r ...

  9. JavaScript中函数防抖、节流

    码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14565642.html 目录 码文不易,转载请带上本文链接,感谢~ https://www ...

  10. JavaScript中的防抖与节流-图文版

    01.防抖还是节流 防抖 与 节流 目的都是避免一定时间内,大量重复的操作造成的性能损耗.因此原理也类似,都是阻止过多的事件执行,只保留一部分来执行.适用场景略有不同,也有交叉,动手练习一遍就懂了. ...

随机推荐

  1. 地址标准化服务AI深度学习模型推理优化实践

    简介: 深度学习已在面向自然语言处理等领域的实际业务场景中广泛落地,对它的推理性能优化成为了部署环节中重要的一环.推理性能的提升:一方面,可以充分发挥部署硬件的能力,降低用户响应时间,同时节省成本:另 ...

  2. MaxCompute 公共云多租户设计的技术要点详解及产品实现特色

    ​简介:公共云大数据平台在多租户的设计和实现方式上有所差异.本文主要介绍在公共云大数据平台的多租实现方案中需要考虑的问题和挑战,重点介绍了MaxCompute在计算和存储多租实现上的特点.期望通过这些 ...

  3. Serverless Kubernetes:理想,现实与未来

    简介: 当前 Serverless 容器的行业趋势如何?有哪些应用价值?如果 Kubernetes 天生长在云上,它的架构应该如何设计?Serverless 容器需要哪些基础设施?阿里云容器服务产品负 ...

  4. 一年增加 1.2w 星,Dapr 能否引领云原生中间件的未来?

    简介: 虽然 Dapr 在国外有很高的关注度,但在国内知名度非常低,而且现有的少量 Dapr 资料也偏新闻资讯和简单介绍,缺乏对 Dapr 的深度解读.在 Dapr v1.0 发布之际,我希望可以通过 ...

  5. Flink 1.13,面向流批一体的运行时与 DataStream API 优化

    简介: 在 1.13 中,针对流批一体的目标,Flink 优化了大规模作业调度以及批执行模式下网络 Shuffle 的性能,以及在 DataStream API 方面完善有限流作业的退出语义. 本文由 ...

  6. 快速上手 Serverless | 入门第一课

    简介: 本文从云计算抛砖引玉,详解 Serverless 的典型应用场景和一些产品介绍. 一. 从云计算到 Serverless 自世界上第一台通用计算机 ENIAC (图左)诞生以来,计算机科学与技 ...

  7. boom lab分析

    单步调试: (gdb) bt #1 0x0000000000401347 in strings_not_equal () #2 0x0000000000400eee in phase_1 () #3 ...

  8. WPF 将 StaticResource 和 ResourceDictionary 放在一起的魔幻行为

    本文将记录一些在 WPF 里面,使用 StaticResource 将 ResourceDictionary 玩坏的做法.大家可以放心的是,这些玩法基本只有高级玩家或逗比开发者才会使用到 后加入的资源 ...

  9. WPF 制作一个加密文件夹应用

    我有一个需求就是将我的一些文件夹的内容同步到网盘上面去.但是我是不信任现在的各个网盘的,网盘的数据被我认为是会被泄露的数据,我需要同步的文件夹中,可能存在隐私的数据.于是我就想到了将文件夹里面的内容进 ...

  10. Docker的基本命令

    1.docker使用的优点 1.更快速的交付和部署 对于开发和运维人员来说,最希望的是保持所有环境一致,这样不会导致,开发在自己的环境里程序运行正常而运维跑的服务器环境里就不正常:对于运维来说,可以使 ...