节流(Throttle)是一种常用的前端性能优化手法,他的主要目的是控制频繁触发的事件
在前端中,有些事件会频繁触发,如果每次事件触发都执行一些复杂的操作,会导致页面变得卡顿,影响用户体验。而通过节流技术,可以控制事件的触发频率,从而减少不必要的操作,提高页面性能和用户体验。
下面我们以鼠标移动这个事件为例,来说明节流的思路和效果
我们的demo代码如下:
<script>
// 节流函数
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = new Date().getTime();
if (now - lastCall >= delay) {
func.apply(this, args);
lastCall = now;
}
}
}
// 普通鼠标移动处理
function handleNormalMouseMove(event) {
...
}
// 节流鼠标移动处理
const handleThrottledMouseMove = throttle(function(event) {
...
}, 200);
这是一个简单的节流方案,思路其实就是对原函数做一个装饰器,这个装饰器的作用是延迟函数的触发,以此达到减少函数触发频率的效果
上述代码中,throttle 函数接收两个参数:要节流的函数 fn 和节流延迟时间 delay,并返回一个新的函数。
每当新的 fn 被调用时,throttle 函数会记录当前时间 now。如果上一次调用 fn 的时间和 now 的时间差小于 delay,则会清除上一个定时器并再次设置一个新的定时器,以延迟调用 fn 函数。否则,直接调用 fn 函数。
这样做可以确保 fn 函数最多每隔 delay 毫秒被调用一次,以避免在高频事件中频繁执行代码导致的性能问题。
效果如图所示,在相同的时间内,对于鼠标移动这个事件,普通的函数触发了1160次,而我们的节流版本仅仅触发了62次,对于位置来说,这个版本的节流函数并不能准确反馈最后一次鼠标所在位置,但用于说明节流的思想足够了。
此外,节流还有基于浏览器帧(RAF, request animation frame)和setTimeout的方法
// RAF节流装饰器
function rafThrottle(fn) {
let rafId = null;
return function(...args) {
if (rafId === null) {
rafId = requestAnimationFrame(() => {
fn.apply(this, args);
rafId = null;
});
}
}
}
// setTimeout节流装饰器
function setTimeoutThrottle(fn, delay = 200) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
}
}
setTimeout和之前的方法一样,都是设置时间延迟,
RAF方法则是自动匹配浏览器渲染机制,在浏览器绘制下一帧的时候执行原函数,以此来减少不必要的函数调用
在这个示例中,RAF方法和普通事件效果一样,我们可以加上延迟机制,强行让函数慢下来
最后,我们使用chrome f12自带的开发者工具来看一下这个demo中各个节流方法的性能表现
我们着重关注event log
打开一小段时间,观察鼠标移动后发生了什么,实际上可以看到delay和setTimeout是开销最小的两种节流方法,尤其是setTimeout,raf因为要request animation frame,反而自带一些固定的成本
综上,我们介绍了三大节流方法,这其实是前端入门必修课
节流(Throttle)本质上是通过延迟函数,来实现减少函数触发频率的技巧
使用方法类似装饰器,实现方法包括:
- 手动判断now-lastCall > delay
- setTimeout(推荐)
- RAF 在某些情况可以比较使用
此外,我们也总结一下使用chrome f12来分析节流性能的基本流程
- 打开chrome f12 devtools
- 找到performance选项
- 进入,点击record按钮
- 运行一段时间
- 点击stop
- 打开一小段时间
- 关注event log
- 注意分析event log中代码的运作机制,找到你的分析目标