防抖和节流
防抖
在准备执行某项操作时,预设一个间隔时间,然后计时,达到预设的时间间隔之后才执行逻辑;
在没到达间隔时间这段时间内,如果有多次重复操作,那么后面的操作会顶替掉前面的计时任务,重新开始计时。
应用场景: 输入框输入内容过程中去服务器查询数据,不能每次input都去查询,所以只有当用户输入停顿一定时间间隔时,认为用户希望获得查询结果。
代码如下:
/**
* 防抖: 重复操作重置定时器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重复进入时,设置为false,用于 run内部进行判断
// 若没有此标志位,run内只能用 immediate 来判断优化
// 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
isExecuted = false;
// 定时器存在时,清空并重建
// 没有必要释放timer,因为run方法会重新赋值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的两种情况
// 1. 初始状态,还没有创建任何定时器
// 2. 完成了一次执行,timer被释放
// 所以当timer不存在时,需要创建;
// timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
!!immediate && callback.apply(context, args);
isExecuted = true; // 标记为已执行
run();
}
}
}
测试代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
}
</style>
</head>
<body>
<input id="input" type="text">
<span>input输入结果</span>
<script>
window.onload = () => {
var inputEl = document.getElementById('input');
inputEl.addEventListener('input', debounce(function(e) {
this.nextElementSibling.textContent = this.value
}, 1000, true));
}
/**
* 防抖: 重复操作重置定时器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重复进入时,设置为false,用于 run内部进行判断
// 若没有此标志位,run内只能用 immediate 来判断优化
// 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
isExecuted = false;
// 定时器存在时,清空并重建
// 没有必要释放timer,因为run方法会重新赋值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的两种情况
// 1. 初始状态,还没有创建任何定时器
// 2. 完成了一次执行,timer被释放
// 所以当timer不存在时,需要创建;
// timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
!!immediate && callback.apply(context, args);
isExecuted = true; // 标记为已执行
run();
}
}
}
</script>
</body>
</html>
节流
当短时间内重复执行某项操作时,予以忽略,只执行一次;知道执行完成之后才会重新添加执行能力。
实现原理: 维护一个定时器,每次执行操作是都判断定时器是否存在,如果定时器存在,直接return;
如果定时器不存在,则创建定时器,定时器到期后执行,并清除定时器和定时器标志
代码如下:
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 仅在不是立即模式时执行,防止二次执行
// 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
if (!immediate) callback.apply(context, args)
clearTimeout(timer) // 清除定时器,
timer = null // 回收timer,防止对后面的执行造成影响
}, delay)
}
return function() {
// 存储上下文和实参列表
context = this;
args = arguments;
// 当前如果有定时器任务,则取消操作
if (timer) return
// 如果需要立即执行,则执行
if (immediate) callback.apply(context, arguments)
// 再次执行run方法创建定时器用于下次判断
run()
}
}
测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
window.onload = () => {
var containerEl = document.getElementById('container');
var i = 0;
containerEl.addEventListener('mousemove', throttle((e) => {
containerEl.textContent = i++
}, 200, true));
}
/**
* 节流
*/
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 仅在不是立即模式时执行,防止二次执行
// 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
!immediate && callback.apply(context, args);
clearTimeout(timer) // 清除定时器,
timer = null // 回收timer,防止对后面的执行造成影响
}, delay)
}
return function() {
// 存储上下文和实参列表
context = this;
args = arguments;
if (timer) return; // 当前如果有定时器任务,则取消操作
!!immediate && callback.apply(context, args); // 如果需要立即执行,则执行
run() // 再次执行run方法创建定时器用于下次判断
}
}
</script>
</body>
</html>
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流Throttle</title>
<style>
#container {
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
background-color: gray;
color: white;
margin-top: 20px;
}
</style>
</head>
<body>
<input id="input" type="text">
<span>input输入结果</span>
<div id="container"></div>
<script>
window.onload = () => {
var inputEl = document.getElementById('input');
var containerEl = document.getElementById('container');
var i = 0;
inputEl.addEventListener('input', debounce(function(e) {
this.nextElementSibling.textContent = this.value
}, 1000, true));
containerEl.addEventListener('mousemove', throttle((e) => {
containerEl.textContent = i++
}, 200, true));
}
/**
* 防抖: 重复操作重置定时器
*/
function debounce (callback, delay, immediate) {
let timer, context, args, isExecuted;
let run = () => {
timer = setTimeout(() => {
!isExecuted && callback.apply(context, args);
clearTimeout(timer);
timer = null;
}, delay)
}
return function() {
context = this;
args = arguments;
// 重复进入时,设置为false,用于 run内部进行判断
// 若没有此标志位,run内只能用 immediate 来判断优化
// 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
isExecuted = false;
// 定时器存在时,清空并重建
// 没有必要释放timer,因为run方法会重新赋值
if (timer) {
clearTimeout(timer);
run()
} else {
// timer不存在的两种情况
// 1. 初始状态,还没有创建任何定时器
// 2. 完成了一次执行,timer被释放
// 所以当timer不存在时,需要创建;
// timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
!!immediate && callback.apply(context, args);
isExecuted = true; // 标记为已执行
run();
}
}
}
/**
* 节流
*/
const throttle = (callback, delay, immediate) => {
let timer, context, args;
let run = () => {
timer = setTimeout(() => {
// 仅在不是立即模式时执行,防止二次执行
// 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
!immediate && callback.apply(context, args);
clearTimeout(timer) // 清除定时器,
timer = null // 回收timer,防止对后面的执行造成影响
}, delay)
}
return function() {
// 存储上下文和实参列表
context = this;
args = arguments;
if (timer) return; // 当前如果有定时器任务,则取消操作
!!immediate && callback.apply(context, args); // 如果需要立即执行,则执行
run() // 再次执行run方法创建定时器用于下次判断
}
}
</script>
</body>
</html>
网友评论