大家好,欢迎来到IT知识分享网。
1、什么是节流
- 在某个时间内(比如500ms),某个函数只能被触发一次;
过程:
- 当事件触发时,相应的函数按照一定的频率去执行
2、节流的应用场景
- 监听页面的滚动事件;
- 鼠标移动事件;
- 用户频繁点击按钮操作;
- 游戏中的一些设计;
总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用;
3、节流函数的实现
下面我们根据需求场景,一步一步深入实现节流函数
3.1 节流基本功能
节流函数的默认实现思路我们采用时间戳的方式来完成:
- 我们使用一个last来记录上一次执行的时间
- 每次准备执行前,获取一下当前的时间now:
now - last > interval - 那么函数执行,并且将now赋值给last即可
- 每次准备执行前,获取一下当前的时间now:
代码实现 :
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @returns */ function throttle(fn, interval) { // 1.记录上一次的开始时间 let lastTime = 0 // 2.事件触发时, 真正执行的函数 const _throttle = function () { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长时间需要去触发函数 const remainTime = interval - (nowTime - lastTime) //第一次会执行,原因是nowTime刚开始是一个很大的数字,结果为负数 //若最后一次没能满足条件,不会执行 if (remainTime <= 0) { // 2.3.真正触发函数 fn() // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`)
}
inputEl.oninput = throttle(inputChange, 1000)
</script>
</body>
</html>
3.2 绑定this和参数
我们知道在oninput事件触发时会有参数传递,并且触发的函数中this是指向当前的元素节点的
- 目前我们fn的执行是一个独立函数调用,它里面的this是window
- 我们需要将其修改为对应的节点对象,而返回的function中的this指向的是节点对象;
- 目前我们的fn在执行时是没有传递任何的参数的,它需要将触发事件时传递的参数传递给fn
- 而我们返回的function中的arguments正是我们需要的参数;
所以我们的代码可以进行如下的优化:
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @returns */ function throttle(fn, interval) { // 1.记录上一次的开始时间 let lastTime = 0 // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长时间需要去触发函数 const remainTime = interval - (nowTime - lastTime) //第一次会执行,原因是nowTime刚开始是一个很大的数字,结果为负数 //若最后一次没能满足条件,不会执行 if (remainTime <= 0) { // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
}
inputEl.oninput = throttle(inputChange, 1000)
</script>
</body>
</html>
3.3 节流函数第一次触发,优化立即执行
目前我们的函数触发第一次是都是立即执行的,但是某些场景是用户开始输入时的第一次是不立即执行的,我们可以如何优化呢?
- 我们可以让用户多传入一个参数:leading
- leading为true时,或者不传(默认为true),第一次触发会立即执行
- leading为false时,第一次不会立即执行
- 我们可以根据是否传入leading进行不同的处理方式:
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true }) { // 1.记录上一次的开始时间 let lastTime = 0 const {leading} = options // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() //当lastTime为0时且第一次不执行,此时 remainTime = interval - (nowTime - lastTime) = interval > 0 if (!lastTime && !leading) lastTime = nowTime//决定第一次是否执行函数 // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
}
inputEl.oninput = throttle(inputChange, 2000, { leading: false })
</script>
</body>
</html>
3.4 优化节流函数最后一次执行
默认情况下,我们的防抖函数最后一次是不会执行的
- 因为没有达到最终的时间,也就是条件
now - last > interval满足不了的 - 但是,如果我们希望它最后一次是可以执行的,那么我们可以让其传入对应的参数来控制
我们来看一下代码如何实现:
- 我们增加了判断语句:
- 当最后一次需要执行且没有定时器时,只需要创建一个定时器,并且还有多久触发由remainTime决定
- 如果固定的频率中执行了回调函数
- 因为需要执行回调函数,所以如果存在定时器则需要取消;
- 并且将timer赋值为null,这样的话可以开启下一次定时器;
- 如果定时器最后执行了,那么timer需要赋值为null
- 因为下一次重新开启时,只有定时器为null,才能进行下一次的定时操作;
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true, trailing: false,}) { const { leading, trailing } = options; // 1.记录上一次的开始时间 let lastTime = 0; let timer = null; // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime(); //当lastTime为0时且第一次不执行 if (!lastTime && !leading) lastTime = nowTime; // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime); if (remainTime <= 0) { if (timer) { //执行函数时,需要取消定时器 clearTimeout(timer); timer = null; } // 2.3.真正触发函数 fn.apply(this, args); // 2.4.保留上次触发的时间 lastTime = nowTime; return; } //当最后一次需要执行且没有定时器时,只需要创建一个定时器,还有多久触发由remainTime决定 if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; //执行后重置 lastTime = !leading ? 0 : new Date().getTime(); //第一次不执行时为0,第一次执行时为定时器执行时的时间 }, remainTime); } }; return _throttle; }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
}
inputEl.oninput = throttle(inputChange, 2000, { leading: true , trailing: true})
</script>
</body>
</html>
3.5 当我们触发函数时,在未到执行时间,可以取消函数执行
与防抖函数的取消功能是类似的:
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const {leading,trailing} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args) timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }
代码调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const throttleChange = throttle(inputChange, 2000, { leading: false, trailing: true })
inputEl.oninput = throttleChange
// 取消功能
cancelBtn.onclick = function () {
throttleChange.cancel()
}
</script>
</body>
</html>
3.6 当触发的函数有返回值时,获取在节流函数中执行的结果
与防抖函数类似,有两种方法:
- 方法一:通过回调函数
- 方法二:通过Promise的resolve
(1)给throttle函数的options,多添加一个参数,参数为一个回调函数:
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const { leading,trailing,resultCallback} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 const result = fn.apply(this, args) if (resultCallback && typeof resultCallback === 'function') resultCallback(result) //函数回调返回返回值 // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { const result = fn.apply(this, args) if (resultCallback && typeof resultCallback === 'function') resultCallback(result) //函数回调返回返回值 timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
// 返回值
return "aaaaaaaaaaaa"
}
// 方法一 通过添加一个函数参数来获取
const throttleChange = throttle(inputChange, 1000,{
leading: false,
trailing: true,
resultCallback: function (res) {
console.log("resultCallback:", res)
}
})
inputEl.oninput = throttleChange
// 取消功能
cancelBtn.onclick = function () {
throttleChange.cancel()
}
</script>
</body>
</html>
(2)使用Promise来返回执行结果:
/ * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const { leading,trailing} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { return new Promise((resolve, reject) => { //promise方式返回返回值 // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 const result = fn.apply(this, args) resolve(result) // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { const result = fn.apply(this, args) resolve(result) timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } }) } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
// 返回值
return "aaaaaaaaaaaa"
}
// 方法二 返回一个promise
const throttleChange = throttle(inputChange, 3000, {
leading: false,
trailing: true
})
const tempCallback = function (...args) {
throttleChange.apply(this, args).then(res => {//此时this绑定的是input对象
console.log("Promise的返回值结果:", res)
})
}
inputEl.oninput = tempCallback
// 取消功能
cancelBtn.onclick = function () {
throttleChange.cancel()
}
</script>
</body>
</html>
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/118478.html
