前端性能优化-每一个前端开发者需要知道的防抖与节流知识

作者: 五月君 来源:编程界

防抖和节流都是应用在高频事件触发场景中,例如 scroll(滚动加载、回到顶部)、input(联想输入) 事件等。防抖和节流核心实现思想是在事件和函数之间增加了一个控制层,达到延迟执行的功能,目的是防止某一时间内频繁执行一些操作,造成资源浪费。

事件与函数之间的控制层通常有两种实现方式:一是使用定时器,每次事件触发时判断是否已经存在定时器,是本文我们实现的方式。另外一种是记录上一次事件触发的时间戳,每次事件触发时判断当前时间戳距离上次执行的时间戳之间的一个差值(deplay - (now - previous)),是否达到了设置的延迟时间。

可视化效果对比

下图是通过一个可视化工具 debounce_throttle 截取的一个效果图,展示了移动鼠标事件在常规操作、防抖处理(debounce)、**节流处理(throttle)**三种情况下的一个对比。

防抖(debounce)

防抖是在事件触的指定时间后执行回掉函数,如果指定时间内再次触发事件,按照最后一次重新计时。

生活场景示例:公交车到站点后,师傅不会上一个人就立马关闭车门起步,会等待最后一个人上去了或车上人已经满了,才会关闭车门起步。

联想输入 - 常规示例

例如搜索框联想提示,当我们输入数据后,可能会请求接口获取数据,如果没有做任何处理,当在输入开始时就会不断的触发接口请求,这中间必然会造成资源的浪费,如果这样频繁操作 DOM 也是不可取的。

// Bad code

<html>

<body>

<div> search: <input id="search" type="text"> </div>

<script>

const searchInput = document.getElementById("search");

searchInput.addEventListener('input', ajax);

function ajax(e) { // 模仿数据查询

console.log(`Search data: ${e.target.value}`);

}

</script>

</body>

</html> 上面这段代码我们没有做过任何优化,使用 ajax() 方法模拟数据请求,让我们看下执行效果。

常规联想输入操作.gif

如果是调用的真实接口,从输入的那一刻起就会不停掉用服务端接口,浪费不必要的性能,还很容易触发接口的限流措施,例如 Github 提供的 API 会有每小时最大请求数限制。

联想输入 - 防抖处理示例

让我们实现一个防抖函数(**debounce****)**优化下上面的代码。**原理是通过标记,判断指定的时间内是否存在多次调用,当存在多次调用时清除掉上一次的定时器,重新开始计时,在指定的时间内如果没有再次调用,就执行传入的回调函数 ****fn**。

function debounce(fn, ms) {

let timerId;

return (...args) => {

if (timerId) {

clearTimeout(timerId);

}

timerId = setTimeout(() => {

fn(...args);

}, ms);

}

} 这对于搜索场景是比较合适的,我们希望以最后一次输入结果为准,修改最开始的联想输入示例。

const handleSearchRequest = debounce(ajax, 500)

searchInput.addEventListener('input', handleSearchRequest); 这次就好多了,当连续输入停顿时以最后一次的输入接口为准请求接口,避免了不停的刷新接口。

联想输入-防抖.gif

适当的时候记得要清除事件,例如 React 中,我们在组件挂载时监听 input,同样的组件卸载时也要清除对应的事件监听器函数。

componentDidMount() {

this.handleSearchRequest = debounce(ajax, 500)

searchInput.addEventListener('input', this.handleSearchRequest);

}

componentWillUnmount() {

searchInput.removeEventListener('input', this.handleSearchRequest);

}

节流(throttle)

节流是在事件触发后,在指定的间隔时间内执行回调函数。

生活场景示例:当我们乘坐地铁时,列车总是按照指定的间隔时间每 5 分钟(也许是其它时间)这样运行,当时间到达之后,列车就要开走了。

滚动到顶部 - 常规示例

例如,页面有很多个列表项,当我们向下滚动之后希望出现一个 Top 按钮 点击之后能够回到顶部,这时我们需要获取滚动位置与顶部的距离判断是否展示 Top 按钮。

<body>

<div id="container"></div>

<script>

const container = document.getElementById('container');

window.addEventListener('scroll', handleScrollTop);

function handleScrollTop() {

console.log('scrollTop: ', document.body.scrollTop);

if (document.body.scrollTop > 400) {

// 处理展示按钮操作

} else {

// 处理不展示按钮操作

}

}

</script>

</body> 可以看到,如果不加任何处理,滚动一下可能就会触发上百次,每次都去做处理,显然是白白浪费性能的。

滚动未处理节流.png

滚动到顶部 - 节流处理示例

实现一个简单的节流(throttle)函数,与防抖很相似,区别的地方是,这里通过标志位判断是否已经被触发,当已经触发后,再进来的请求直接结束掉,直到上一次指定的间隔时间到达且回调函数执行之后,再接受下一个处理。

function throttle(fn, ms) {

let flag = false;

return (...args) => {

if (flag) return;

flag = true;

setTimeout(() => {

fn(...args)

flag = false;

}, ms);

}

} 改造下上面的示例,再来看看执行结果。

const handleScrollTop = throttle(() => {

console.log('scrollTop: ', document.body.scrollTop);

// todo:

}, 500);

window.addEventListener('scroll', handleScrollTop); 与上面 “常规滚动到顶部示例” 做对比,现在效果已经好多了。

滚动到顶部-节流处理.gif

记得清除事件

以 React 为例,组件挂载时我们监听 window 的 scroll 事件,在组件卸载时记得要移除对应的事件监听器函数。如果组件卸载时忘记移除,原先 A 页面引入了 ScrollTop 组件,单页面应用跳转到 B 页面后,虽然 B 页面没有引入 ScrollTop 组件,但是也会受到影响,因为该事件已经在 window 全局对象注册了,另外这样也存在内存泄漏。

class ScrollTop extends PureComponent {

componentDidMount() {

this.handleScrollTop = throttle(this.props.updateScrollTopValue, 500);

window.addEventListener('scroll', this.handleScrollTop);

}

componentWillUnmount() {

window.removeEventListener('scroll', this.handleScrollTop);

}

// ...

}

requestAnimationFrame

requestAnimationFrame 是浏览器提供的一个 API,它的应用场景是告诉浏览器,我需要运行一个动画。该方法会要求浏览器在下次重绘之前调用指定的回调函数更新动画。这个 API 在 JavaScript 异步编程指南 - 探索浏览器中的事件循环机制 中有讲过。

它会受到浏览器的刷新频率影响,如果是 60fps 那就是每间隔 16.67ms 执行一次,如果在 16.67ms 内有多次 DOM 操作,也是不会渲染多次的。

当浏览器的刷新频率为 60fps 时等价于 throttle(fn, 16.67)。在使用时需要先处理下,不能让它立即执行,由事件触发。

const handleScrollTop = () => requestAnimationFrame(() => {

console.log('scrollTop: ', document.body.scrollTop);

// todo:

});

window.addEventListener('scroll', handleScrollTop); requestAnimationFrame 这个是浏览器的 API,在 Node.js 中是不支持的。

社区工具集支持

社区中一些 JavaScript 的工具集框架,也都提供了防抖与节流的支持,例如 underscorejs、lodash。

刚开始有提到,另外一种实现方式是记录上一次事件触发的时间戳,每次事件触发时判断当前时间戳距离上次执行的时间戳之间的一个差值,来判断是否达到了设置的延迟时间,以 underscorejs throttle 实现为例,只保留部分代码示例,一个关键代码片段是 remaining = wait - (_now - previous)。

// https://github.com/jashkenas/underscore/blob/master/modules/throttle.js#L23

export default function throttle(func, wait, options) {

var timeout, context, args, result;

var previous = 0;

var throttled = function() {

var _now = now();

if (!previous && options.leading === false) previous = _now;

var remaining = wait - (_now - previous);

context = this;

args = arguments;

if (remaining <= 0 || remaining > wait) {

if (timeout) {

clearTimeout(timeout);

timeout = null;

}

previous = _now;

result = func.apply(context, args);

if (!timeout) context = args = null;

}

};

return throttled;

}

总结

防抖是在事件触的指定时间后执行回掉函数,如果指定时间内再次触发事件,按照最后一次重新计时。节流是在事件触发后的间隔时间内执行回调函数。这两个概念在前端开发中都是会遇到的,选择合理的方案解决实际问题。

防抖与节流还不是太理解的,对着文中的示例自己实践下,有什么疑问在留言区提问。

Reference

https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/

http://demo.nimius.net/debounce_throttle/

JavaScript 异步编程指南 - 探索浏览器中的事件循环机制

原文地址:https://www.toutiao.com/article/7040983577663930911/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


所以很多人都会选择将这些生活琐事来交给智能化产品,在众多产品中,扫拖机器人所给我们带来的便利性最强,扫地、拖地全都一气呵成,不需要人为过多干预,不过目前大多数扫拖机器人对于双手解放得不彻底。而石头作为
“昨天的经历都将成为明天的力量。” 将绝大部分精力都奉献给璃月港的刻晴,可以说是《原神》中的高人气角色了,虽然是常驻角色,并且对于普通玩家来说……刻师傅别刮了……不过作为开服就存在的角色,还有独特的剑法
最近,全球知名的通信产业盛会MWC 2024(2024世界移动通信大会)正式召开,其中,联发科以“连接AI宇宙”(Connecting the AI-verse)为主题,为大众展示出一系列在AI和移动通信技术等领域的最新突破,吸引了大量行业
今年上半年有很多值得关注的机型,其中华为最新的影像旗舰华为P70 Art也自然受到了业界不少的关注目光,目前关于这款机型的轮廓图已经在网上曝光。
目前,2024世界移动通信大会(MWC)正在西班牙巴塞罗那举行,值得一提的是,此次大会参展中国厂商非常多,包括华为、中兴、小米、荣耀等等多家厂商均在其列。
就在去年,真我推出了11 Pro+,用一个2亿像素传感器和zoom变焦功能,开启了中端手机影像的长焦大战,而后友商才姗姗来迟的跟进了2亿像素传感器。
【手机之家新闻】一年一度的MWC已经于当地时间2月26日在巴塞罗那正式开展,在本次MWC2024上全球各大厂商齐聚一堂,展出自家最新的技术与产品,其中中兴就参展本次MWC2024,并且展出了诸多面向企业端的产品,而旗下的
近日,联发科在MWC 2024(2024 世界移动通信大会)上展出了一系列令人瞩目的AI和移动通信技术突破,以“连接AI宇宙”(Connecting the AI-verse)的展厅吸引了无数业界精英和媒体的目光。特别是其现场的生成式AI技术
虽然目前国内已经有不少厂商入局折叠屏产品,但是努比亚却迟迟没有入局。不过在近日举办的MWC 2024展会上,努比亚发布了自家首款折叠屏手机——努比亚Flip,预计国内很快也会上市。
MWC 2024正在西班牙巴塞罗那举办,和往年一样,荣耀这次依旧携众多新产品、新技术参会。荣耀Magic6 Pro、荣耀Magic V2 RSR保时捷设计的机型在海外正式发布,并且还展示了魔法大模型、任意门等诸多新技术。
MWC 2024正在西班牙巴塞罗那如火如荼地举行,其中小米也参加了今年的大会,在会上发布了在国内大受欢迎的小尺寸旗舰——小米14。值得一提的是,高通公司CEO安蒙甚至亲临发布会现场为这款机型助阵。
《原神》是一直以来在机圈深受欢迎的游戏,在充满幻想的提瓦特大陆上,你可以邂逅不少性格迥异、能力独特的伙伴。而一加Ace系列一直就拥有非常强烈的电竞属性,也是畅玩《原神》的热门机型,而在本月,一加Ace 3将推
有不少网友发现,今年新机的发布时间相对于往年大幅提前,很多厂商在春节之前密集发布了自己最新的中高端机型,给人一种年后没什么新机可发了的感觉。不过魅族全新的大杯机型——魅族21 PRO非常值得期待,魅族科技也
2022年7月,小米12S Ultra正式发布,这款产品率先将1英寸大底主摄引入到移动影像领域,同时凭借鲜明的徕卡影调给人留下深刻的印象,同时这款产品也被视为了影像旗舰地位的机型。如果从那时算起,到现在差不多已经快过
随着智能手机的日益普及和智能化进程的加速,智能穿戴设备成为了人们关注的焦点。各大智能手机厂商纷纷进军智能穿戴市场,试图在这一新兴领域抢占先机。
早在去年秋天,HyperOS操作系统发布的时候,小米便勾勒出了“人车家全生态”的美好蓝图,而在这其中,小米的多终端统一战略是核心,目前已经有不少小米产品预装或者接受到了HyperOS操作系统的推送,在过去几个月的时
今年雷军将把更多的精力放在小米汽车上,所以接下来的手机业务将由刚刚兼任小米品牌总经理卢伟冰接管。同时雷军也在微博上表示小米2024年开年旗舰——小米14 Ultra即将在近期发布,并且将有卢伟冰进行讲解。另外,卢
新的一年有龙则灵,有愿必达。自1月19日起,荣耀加码“新年荣耀,一起成龙”年货节,在全国荣耀线下门店上线了“新年许愿处”、“龙运当头”等趣味活动,吸引大批消费者到店打卡许愿,戴龙头迎好运。与此同时,为了回
小米在官网微博中已经透露了关于小米14 Ultra信息,所以新机上市应该不会太晚。根据德国莱茵的官方消息,目前小米14 Ultra(型号为24030PN60G)获得了莱茵无频闪认证,表明这款手机可以有效减轻屏幕给用户带来的视觉疲
2月22日,上海广播电视台与华为举办鸿蒙合作签约仪式,宣布其官方客户端看看新闻APP将基于HarmonyOS NEXT鸿蒙星河版启动鸿蒙原生应用开发,为用户提供更加极致的新闻资讯服务体验。此次合作标志着上海广播电视台成为全国