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. 【pytorch学习】之数据操作

    1 数据操作 为了能够完成各种数据操作,我们需要某种方法来存储和操作数据.通常,我们需要做两件重要的事:(1)获取数据: (2)将数据读入计算机后对其进行处理.如果没有某种方法来存储数据,那么获取数据 ...

  2. 阿里大数据云原生化实践,EMR Spark on ACK 产品介绍

    开源大数据社区 & 阿里云 EMR 系列直播 第六期   主题:EMR spark on ACK 产品演示及最佳实践   讲师:石磊,阿里云 EMR 团队技术专家 内容框架: 云原生化挑战及阿 ...

  3. Serverless Devs 2.0 开箱测评:Serverless 开发最佳实践

    ​简介: 当下,Serverless 概念很火,很多同学被 Serverless 的优势吸引过来,比如它的弹性伸缩,免运维,高可用,资费少.但真正使用起来去落地的时候发现问题很多,大型项目如何组织函数 ...

  4. [FE] Quasar BEX 不同位置类型的 debug 调试方式

    科普:[FE] Quasar BEX 所有位置类型 types 不同类型调试,查看错误在不同的位置,如下图中的 4 个位置. Refer:https://quasar.dev/quasar-cli/d ...

  5. JavaScript数组Array方法介绍,使用示例及ES6拓展

    数组定义 有次序和编号的一组值 类似数组对象 函数agruments对象,字符串,DOM元素集 实例属性 Array.prototype.length length可以赋值,用以改变数组长度 arr. ...

  6. RTThread 重定义rt_hw_console_output函数

    在学习单片机时,我们会经常使用printf函数进行信息输出,方便调试程序,而学习RT-Thread时也会经常使用rt_kprintf函数进行信息输出,所以在移植完RT-Thread时,我们首先需要定义 ...

  7. 笔记06-第六讲 Cadence同一页面建立电气互连

    笔记06-第六讲 Cadence同一页面建立电气互连 内容: 90度和任意角度连线; 连线方式; 十字线添加/删除连接点; Net alias; 无连接的管脚处理; 注意事项. 连线有两种方式,右侧工 ...

  8. Java获取电脑盘符(最后一个盘符)

    //遍历获得所有盘符 File[] roots = File.listRoots(); for (int i =0; i < roots.length; i++) { System.out.pr ...

  9. C/C++如何写调试宏

    1. 调试宏以及测试 在写代码时,不可避免需要打印提示.警告.错误等信息,且要灵活控制打印信息的级别.另外,还有可能需要使用宏来控制代码段(主要是调试代码段)是否执行.为此,本文提供一种调试宏定义方案 ...

  10. 【Oracle故障处理】ORA-00845: MEMORY_TARGET not supported on this system

    场景:由于需要用RMAN恢复数据库,提取以前的数据表中的数据.虚拟机为节省资源调小了内存,启动数据库报了 如下错误: ORA-00845: MEMORY_TARGET not supported on ...