浏览器中有一些事件会高频率触发,比如:resize(窗口重置)、scroll(页面滚动)、mousemove(鼠标移动)等,监听这些事件,并按浏览器的触发频率响应,极可能造成页面卡顿、抖动,甚至浏览器崩溃,对此有两种解决方案:防抖(debounce ) 和 节流(throttling )。
debounce(防抖)和 throttle(节流)是一种编程技巧,用于控制某个函数在一定时间内执行多少次。主要用于平滑事件响应、减轻浏览器压力。
1、debounce(防抖)
策略:当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 在后期又扩展了前缘debounce,执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。
简单来说,对多次触发事件的函数,在规定时间内,只执行一次 (第一次或最后一次)。
延迟debounce,示意图:
前缘debounce, 示意图:
——图片来源:https://blog.csdn.net/hupian1989
1.1、应用场景
(1)监听浏览器窗口的resize、scroll事件事件(不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,只执行最后一次);
(2)带有Ajax请求的input上输入实时搜索事件(只在用户停止输入时才发送请求);
。。。。。。
1.2、实现防抖
未使用防抖之前:
使用防抖之后:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>debounce</title>
<style>
#test {
height: 150px;
line-height: 150px;
text-align: center;
color: #fff;
background-color: #ccc;
font-size: 80px;
}
</style>
</head>
<body>
<div id="test">感应区</div>
<!-- <script>
function debounce(fn, wait) {
let timer
return function () {
let context = this
let args = arguments
//清除上一次延时器
if (timer) clearTimeout(timer)
//重新设置新的延时器
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
function debounce(fn, wait) {
let timer
return function () {
let context = this
let args = arguments
//清除上一次延时器
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, wait)
if (callNow) fn.apply(context, args)
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('debounce被触发了')
}, 1000)
</script> -->
<script>
/**
* @param fn 需要执行的函数
* @param wait 延迟执行时间(毫秒)
* @param immediate true立即执行,false非立即执行
**/
function debounce(fn, wait, immediate) {
//记录上一次延时器
let timer
return function () {
//this指向debounce
let context = this
//参数,fn,wait
let args = arguments
//如果timer不为null, 清除定时器
if (timer) clearTimeout(timer)
//立即执行
if (immediate) {
var callNow = !timer
//定义wait时间后把timer变为null
timer = setTimeout(() => {
timer = null
}, wait)
//如果callNow为true,执行fn函数
if (callNow) fn.apply(context, args)
} else {
//不立即执行,就每次重新定时
timer = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}
}
let num = 0
function count() {
num++
console.log(num);
}
document.getElementById('test').onmousemove = debounce(count,1000,false)
</script>
</body>
</html>
1.3、哪里有现成的工具?
lodash 里面有:
_.debounce(func, [wait=0], [options={}])
underscore 里面也有:
_.debounce(function, wait, [immediate])
2、throttle(节流)
策略:在固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。
简单来说,对多次触发事件的函数,在规定时间内,只执行一次 。
延迟throttle,示意图:
前缘throttle, 示意图:
——图片来源:https://blog.csdn.net/hupian1989
2.1、应用场景
(1)下拉加载更多(无限滚动)事件(是否滑到底部自动加载更多);
(2)鼠标连续不断地触发某事件(如点击,只在单位时间内只触发一次);
。。。。。。
2.2、实现节流
节流一般有两种方式可以实现,分别是时间戳版 和 定时器版。
时间戳版:
function throttle(fn, wait) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
}
}
定时器版:
function throttle(fn, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args)
}, wait)
}
}
}
2.3、应用示例
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>throttle</title>
<style>
html,body{
height: 500%;
}
</style>
</head>
<body>
<script>
function throttle(fn,delay){
var lastTime = 0;
return function(){
var nowTime = Date.now();
if(nowTime - lastTime>delay){
fn.call(this);
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function(){
console.log('scroll被触发了'+Date.now());
},1000)
</script>
</body>
</html>
2.4、哪里有现成的工具?
lodash 里面有:
_.throttle(func, [wait=0], [options={}])
underscore 里面也有:
_.throttle(function, wait, [options])
根据不同场景选择合适的用法。对于有停顿的高频触发事件建议选择防抖,对于高频触发并且连续的事件,选择节流。