为什么需要节流、防抖
当某个事件频繁触发时,事件处理函数会频繁执行,如果处理函数有一些费时、耗性能的操作,就会导致页面出现卡顿甚至浏览器崩溃,这时就需要
节流
和防抖
什么是节流、防抖
- 节流
当事件频发触发时,事件处理程序每隔一段时间执行一次
- 防抖
如果某个频繁触发的事件在规定的时间内没有再次触发,则执行事件处理程序,如果在这段时间内事件再次触发了,则重新计时
什么时候需要节流、防抖
- scroll
- resize
- 表单输入(input事件)校验
- …
实现
<!DOCTYPE html>
<html lang = "en">
<style>
#wrap .scrollBox, #wrap .list{
float: left;
}
.scrollBox {
width: 40%;
height: 500px;
margin-right: 5%;
border: 1px solid red;
overflow: scroll;
}
.list {
width: 40%;
border: 1px solid red;
}
</style>
<body>
<div id = "wrap">
<div class = "scrollBox"><!-- 内容动态生成 --></div>
<ul class = "list">
<li>我是列表</li>
</ul>
</div>
</body>
<script>
// 防抖函数,不如不理解为什么能防抖,请看下面的解释内容
function debounce (handle, delay) {
let timer = null
return function () {
if (timer) {
// console.log('clearTimeout, clear timer')
clearTimeout(timer)
}
// console.log('setTimeout, set timer')
timer = setTimeout(handle, delay)
}
}
// 事件处理函数,生成一个li节点,并添加到ul节点的末尾
function handle () {
const node = document.createElement('li')
const text = document.createTextNode(Date.now())
node.appendChild(text)
document.getElementsByClassName('list')[0].appendChild(node)
}
// 添加事件监听器,形成了一个闭包
document.getElementsByClassName('scrollBox')[0].addEventListener('scroll', debounce(handle, 500))
// 生成滚动栏的内容,以下部分和上面的html、css只为构造一个实验环境
let content = ''
for (let i = 0; i < 1000; i++) {
content += '在我上面滚动鼠标'
}
const textNode = document.createTextNode(content)
document.getElementsByClassName('scrollBox')[0].appendChild(textNode)
</script>
</html>
debounce
函数为什么会有防抖的功能呢?
document.getElementsByClassName('scrollBox')[0].addEventListener('scroll', debounce(handle, 500))
,这行代码形成了一个闭包,debounce
函数运行以后返回一个函数,而返回的这个函数在scroll事件触发期间(即一直滚动页面)其实一直在不停的执行(可以放开注释掉的两行console.log()
进行测试),比如:第一次开始滚动,第一次执行到debounce
返回的函数,发现!!timer === false
,if
条件不成立,直接执行后面的setTimeout
,设置timer
,但是鼠标在一直滚动,马上debounce
返回的函数又在执行第二次了,发现timer
不为空,if
条件成立,执行clearTimeout
清除timer
,但是这时你会发现刚才设置的定时任务还没有执行,就被清除了,这就达到了防抖的要求,鼠标一直滚动一直重复上面的过程(函数一直在执行),直到停止滚动,停止前一次设置的定时任务不会被清除,规定的延时时间一到,执行handle
(处理程序),下一次开始滚动,清除上一次的timer,接着重复上面的过程
有了防抖的基础,节流这里,就省略掉构建实验环境的html代码了,直接上节流方法的实现,有两种实现方式,分别是时间戳和定时任务
定时任务
// 节流函数
function throttle (handle, delay) {
// 节流的关键点在于timer,timer被赋值以后,只有在定时任务执行以后,timer才会重新被置为null
let timer = null
return function () {
if (!timer) {
// 设置timer
timer = setTimeout(() => {
handle()
// 定时任务执行完毕,置空timer
timer = null
}, delay)
}
}
}
// 事件处理函数
function handle () {
console.log('I am handle function')
}
// 添加事件监听器
document.getElementById('app').addEventListener('scroll', throttle(handle, 1000))
时间戳
// 节流函数
function throttle (handle, delay) {
// 节流的关键所在
let prevTime = Date.now()
return function () {
if (Date.now() - prevTime >= delay) {
// 时间到了以后执行事件处理程序,并重置prevTime
handle()
prevTime = Date.now()
}
}
}
// 事件处理函数
function handle () {
console.log('I am handle function')
}
// 添加事件监听器
document.getElementById('app').addEventListener('scroll', throttle(handle, 1000))
节流和防抖有什么区别
- 共同点
都是限制事件处理程序的执行频率
- 区别
节流: 定时重复执行事件处理程序,不论事件触发有多频繁
防抖:只有事件最后一次触发(指定时间内没有再次触发)才会执行处理程序
应用场景
- search搜索框、表单输入验证等触发
input
事件的操作
只有内容输入完成以后,才执行最后的事件处理程序(比如:搜索内容、验证表单内容)
- 窗口的resize事件
窗口resize停止后,最后执行一次事件处理程序
- 鼠标频繁点击的场景
游戏,比如打地鼠、CS、英雄联盟、王者荣耀等需要频繁点击操作的场景
- 下拉刷新
下拉加载更多,一直下拉,但一段事件内只执行一次事件处理程序(加载内容)