一直以来都直接这两个函数,记了忘,记了忘,今天总结下
日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并成一次,只操作一个精确点,JS把这种方式称为debounce(防抖)和throttle(节流)
首先看下例子
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container {
width: 200px;
height: 200px;
margin: auto;
border: 1px solid black;
text-align: center;
line-height: 200px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = getUserAction;
</script>
</body>
</html>
可以发现,当鼠标在框内运动是,会不断触发事件,在大多数情况下,这显然不是我们想要的。
函数防抖
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行最后一次。一般用于resize, scroll
function debounce(fn, timer) {
// 创建一个计时器
var timeout = null;
return function() {
// 如果计时器有值,则清空该计时器,并重新开始执行计时器
if(timeout !== null) {
clearTimeout(timeout)
}
// 如果计时器无值,则开始执行计时器
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, timer)
}
}
//调用函数
container.addEventListener("mousemove", debounce(getUserAction, 1000))
效果:
函数节流
当持续触发事件时,函数并不会一直触发,只会在规定时间触发(相当于减缓了水流速度)一般用于输入框
function throttle(fn, delay) {
// 创建一个开始时间
let startTime = 0
return function() {
// 创建一个结束时间
let endTime = new Date()
// 当结束时间减去开始时间大于规定时间,就执行函数,并且将开始时间设置为当前时间
if(endTime - startTime >= delay) {
fn.apply(this, arguments)
startTime = new Date()
}
}
}
container.addEventListener("mousemove", throttle(getUserAction, 1000))
注意
- 上面的节流函数其实有一个缺陷,就是第一次执行的时候会直接触发,不会加入到节流行列。因为
startTime
最初为0,所以endTime - startTime >= timer
第一次一定为真,简单的办法就是startTime
最初设置为new Date().getTime()
就可以解决。但是这样其实还有一个伏笔。当你很快触发一次函数,虽不会立即执行,但是里面的endTime
会发生改变,这样endTime - startTime
时间会与timer
越来越接近,会导致该函数在后面第一次被执行的时候在不到规定时间的时候就被触发。所以最好的解决办法就是使用定时器处理。
function throttle(fn, delay) {
let timer = null
return function() {
if(!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
}
两种方法需要进行传参直接在节流或防抖函数后面使用bind方法进行调用
总结
偷了一张图