函数防抖与节流工具
先贴代码与使用方法再讲理论:
class mains {
constructor(arg) {
this.timer, this.flag = true;
}
/**
* 函数防抖(立即执行版与延迟执行)
* @param {Function} func 需要执行的函数
* @param {Number} delay 延迟/等待时间
* @param {Array} args 调用方法的参数
* @param {Boolean} runNow 是否立即执行
* @param other 温馨提示:无参更改runNow市args传[] 即可
*/
debounce(func, delay, args = [], runNow = true) {
clearTimeout(this.timer);
runNow ? (() => {
if (this.flag) {
this.flag = false;
func(...args);
}
this.timer = setTimeout(() => {
this.flag = true;
}, delay);
})() : (() => {
this.timer = setTimeout(() => {
func(...args);
}, delay);
})()
}
/**
* 函数节流(立即执行版与延迟执行)
* @param {Function} func 需要执行的函数
* @param {Number} delay 延迟/等待时间
* @param {Array} args 调用方法的参数
* @param {Boolean} runNow 是否立即执行
*/
throttling(func, delay, args = [], runNow = true) {
if (this.flag) {
this.flag = false;
clearTimeout(this.timer);
runNow ? (() => {
func(...args);
this.timer = setTimeout(() => {
this.flag = true;
},
delay);
})() : (() => {
this.timer = setTimeout(() => {
this.flag = true;
func(...args);
},
delay);
})();
}
}
}
const debounceUtils = new mains();//注意:如果一个方法里同时调用debounce和throttling需要把本行注释单独引入new,否则只会触发后调用的
export default debounceUtils;
vue/uniapp等有模块概念的框架 | 原生 |
---|---|
impor导入文件通过Vue.prototype或其他方式将其挂载至原型或全局上调用debounce(防抖)、throttling(节流)方法传入参数即可或者通过import引入使用 | 使用script引入然后调用debounceUtils.debounce(防抖)或debounceUtils.throttling(节流)方法传入参数即可 |
概述:(理论)
1.防抖(防止某件事在一时间多次触发,例如:在表单提交时请求还未相应,用户以为没点到有重新点了几下)。常用于表单提交以及下单/加入购物车
2.节流(也是为了防止某件事在一时间多次触发,例如饿了么、美团、滴滴抢单或者是搜索商品)。常用于搜索商品/下拉刷新限制/抢单类
区别:
触发时间 | |
---|---|
防抖 | 最后一次触发之后重新计时,倒计时结束之后才能触发下一次 |
节流 | 每隔一段时间(时间结束之后)才能触发下一次 |
总结: 防抖是在最后一次触发之后重新计时,倒计时结束之后才能触发下一次。节流是每隔一段时间(时间结束之后)才能触发下一次
实现思路:
防抖和节流的实现方式有许多种(但网络上大部分都是使用apply来实现的这对vue、uni好像不太友好)。
正题
因为都与时间有关所有使用js中的定时器,“每隔一段时间(时间结束之后),最后一次触发” 所以我们选择使用 setTimeout() 来实现
一、防抖:
(1)当我们使用函数时每调用一次他就会执行一次,我们实现的效果都需要延迟/等待一段时间后才能执行。所以我们把他放入定时器中(这里我们设置时间为2000),这时我们运行是这样的:连续点击5次(从第一次点击之后2秒后执行了我们传入的方法触发了5次,这是因为你点击5次所以就调用了5次【小白能理解吧😄】)
实现效果是这样的
//相关代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
//防抖函数
function debounce(func, delay) {
setTimeout(() => {
func();
}, delay)
}
// 点击事件
function add() {
debounce(function () {
console.log(1111111)
}, 2000)
}
</script>
</body>
</html>
(2)回顾一下【总结】在最后一次触发之后,所以这时我们的思路时使用cleaTimeout()方法把之前的定时器清掉只留下最后一个触发的(由于在js中有函数作用域所以我们不能将定时函数放在函数【debounce】中)这时我们的代码是这样的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
//防抖函数
function debounce(func, delay) {
clearTimeout(timer)
timer = setTimeout(() => {
func();
}, delay)
}
// 点击事件
function add() {
debounce(function () {
console.log(1111111)
}, 2000)
}
</script>
</body>
</html>
实现效果是这样的:(嗯?有点内味了。好吧差不多就这样把)
(3)先放下之前的,我们再考虑一下如何实现立即执行版本的?【思考ing… 立即执行版就是一调用他就先执行函数,再进行倒计时,倒计时结束之后才能触发下一次,如果倒计时还没结束又被调用了那就重新开始倒计时】嗯?这么简单?那我们就这样吧:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个置换变量,用来判断是否可以执行函数。
//防抖函数
function debounce(func, delay) {
clearTimeout(timer)
if (flag) {
// 进入这里说明可以调用了
flag = false;
func();
}
timer = setTimeout(() => {
flag = true;
}, delay)
}
// 点击事件
function add() {
debounce(function () {
console.log(1111111)
}, 2000)
}
</script>
</body>
</html>
实现效果是这样的:(嗯?好像又有点内味了。好吧差不多又就这样把)
(4)最后合并一下立即执行版和非立即执行版:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个Boolean变量,用来判断是否可以执行函数。
//防抖函数
function debounce(func, delay, runNow=false) {
clearTimeout(timer)
runNow ? (() => {
if (flag) {
// 进入这里说明可以调用了
flag = false;
func();
}
timer = setTimeout(() => {
flag = true;
}, delay)
})() : (() => {
timer = setTimeout(() => {
func();
}, delay)
})()
}
// 点击事件
function add() {
debounce(function () {
console.log(1111111)
}, 2000)
}
</script>
</body>
</html>
二、节流
(1)第一步思路也和防抖1类似
(2)我们再回顾一下【总结】每隔一段时间才能触发emm… 这个不是和防抖立即执行版类似么(倒计时结束之后才能触发)所以我们把防抖立即执行版的代码复制下来稍稍更改一下不就行了么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个置换变量,用来判断是否可以执行函数。
//节流函数
function throttling(func, delay, runNow = false) {
if (flag) {
flag = false;
clearTimeout(timer)
timer = setTimeout(() => {
func();
flag = true;
}, delay)
}
}
// 点击事件
function add() {
throttling(function () {
console.log(1111111)
}, 1000)
}
</script>
</body>
</html>
实现效果是这样的:
(2)立即执行版?立即执行版不就是触发之后就执行嘛(不就是在非立即执行版上稍稍改动一下位置就行了么?)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个置换变量,用来判断是否可以执行函数。
//节流函数
function throttling(func, delay, runNow = false) {
if (flag) {
flag = false;
func();
clearTimeout(timer)
timer = setTimeout(() => {
flag = true;
}, delay)
}
}
// 点击事件
function add() {
throttling(function () {
console.log(1111111)
}, 1000)
}
</script>
</body>
</html>
实现效果是这样的:
(3)最后合并一下就是这样的了?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个置换变量,用来判断是否可以执行函数。
//节流函数
function throttling(func, delay, runNow = true) {
if (flag) {
flag = false;
clearTimeout(timer)
runNow ? (() => {
func();
timer = setTimeout(() => {
flag = true;
}, delay)
})() : (() => {
timer = setTimeout(() => {
func();
flag = true;
}, delay)
})()
}
}
// 点击事件
function add() {
throttling(function () {
console.log(1111111)
}, 1000)
}
</script>
</body>
</html>
三、最后(我们思考一下调用函数是否可能会需要传参呢?)所有就再增加一个形参吧!
最后的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖</title>
</head>
<body>
<button id="btn" onclick="add()">提交</button>
<script>
let timer;
let flag = true;//定义一个置换变量,用来判断是否可以执行函数。
//防抖函数
function throttling(func, delay, args = [], runNow = true) {
if (flag) {
flag = false;
clearTimeout(timer)
runNow ? (() => {
func(...args);
timer = setTimeout(() => {
flag = true;
}, delay)
})() : (() => {
timer = setTimeout(() => {
func(...args);
flag = true;
}, delay)
})()
}
}
//防抖函数
function debounce(func, delay, args = [], runNow = false) {
clearTimeout(timer)
runNow ? (() => {
if (flag) {
// 进入这里说明可以调用了
flag = false;
func(...args);
}
timer = setTimeout(() => {
flag = true;
}, delay)
})() : (() => {
timer = setTimeout(() => {
func(...args);
}, delay)
})()
}
// 点击事件
function add() {
// 调用外部方法
debounce(runMethord, 1000, [666], true)
}
// 要调用/触发的方法
function runMethord(num) {
console.log(num);
}
</script>
</body>
</html>
PS:上面的代码会有一个小小的bug(这里不描述),所以我们将其封装成class就可以很好的使用