说白话:
节流就像我们喝水上厕所一样... Emm咱们还是换个例子吧。。。
比如说吃饭。吃饭说明规定,五个小时吃一次。吃了一次饭,小狗蹦蹦哒哒地玩了五个小时。五个小时一到, 小狗再回来吃饭。依次类推,每五小时回来吃一次饭。
说人话:
定义:如果持续触发事件,单位时间内执行一次函数。
节流模样:
代码语言:javascript复制<!-- <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script>
<div class="box"></div>
<script>
let obox = document.querySelector('.box')
function todo(e) {
console.log(this, e);
}
let throttleFn = _.throttle(todo, 1000)
obox.onmousemove = throttleFn
</script>
1.gif
我们可以直接使用lodash.js或者underscore.js中的节流函数,查看节流的效果。我的鼠标一直在div中移动,节流就会每个一段时间打印一次。
扒开面具见真相
对于我们而言,光知其然,是远远不够的;我们更要知其所以然! 老样子,咱们给自己上一课吧!
对于节流函数,与防抖的形参类似。它有三个参数:节流的执行函数fn;需要延迟的毫秒数wait;第三个参数options禁用第一次首先执行可以传递{leading: false}
,禁用最后一次执行{trailing: false}
。
那么根据第三个,可以传递两个属性参数。可能会出现三种情况:
- 第一次先执行,最后一次不执行
- 第一次不执行,最后一次执行
- 第一次先执行,最后一次也执行
有头的
第一次先执行。
代码语言:javascript复制function throttle(fn, wait) {
let previous = 0
return function (...args) {
let now = new Date().getTime() || Date.now()
if (now - previous > wait) {
fn.apply(this, args)
previous = now
}
}
}
我们可以使用时间戳的方法去实现第一次触发先执行。先记录默认时间点(一开始为0),在执行函数时,求得当前的时间戳。两者间隔大于等待时间时,就执行fn函数。这样就能够保证第一次触发就能够先执行。 但是一定要将记录当前时间点的值赋给默认时间点,不然,鼠标移动时会一直触发函数执行。
2.gif
有尾巴的
在时间段尾部执行。
代码语言:javascript复制function throttle(fn, wait) {
let timer = null
return function (...args) {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, wait)
}
}
考虑在尾部执行的情况。我们可以联想到定时器setTimeout
,每隔一段时间触发执行一次。如果刚进来的话,正好在上一次执行的时间间隔内,就直接返回。只有当达到预定时间时(没有定时器,刚好要执行),执行函数,但是执行完成后一定要记得清空定时器,以免耽误下一次事件触发。
有头又有尾
嘿,这部就像我们做人一样嘛,有始有终! 根据第三个参数进行判断头尾。再把时间戳版和定时器版两者东拼拼西凑凑,大体模型就出来拉。
代码语言:javascript复制function throttle(fn, wait, options) {
let previous = 0
let timer = null
return function (...args) {
let now = Date.now()
// 先执行
// 先执行里面要判断leading情况
if (!previous && options.leading === false) previous = now // 如果options.leading是后执行 在判断时间间隔时忽略
if (now - previous > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(this, args)
previous = now
}
// 后执行
if (!timer && options.trailing) { // 无定时器
timer = setTimeout(() => {
// 后执行里面要判断leading的情况
previous = options.leading ? Date.now() : 0
fn.apply(this, args)
timer = null
}, wait)
}
}
console.log(previous);
}
let obtn = document.querySelector('#btn')
let obox = document.querySelector('.box') // 按钮
function todo(e) {
console.log(this, e);
}
let throttleFn = throttle(todo, 1000, { leading: false, trailing: false })
obox.onmousemove = throttleFn
加点装饰
上面已经算基本完成了节流的实现。此外,和防抖类似,还有执行函数有返回值的结果、取消节流的功能。
返回值的话,可以使用一个变量去接收执行函数返回值。取消节流在函数上添加一个取消功能函数(取消时清除定时器并且将一开始时间置0)。
代码语言:javascript复制function throttle(fn, wait, options) {
let timer = null, previous = 0, result
let throttled = function (...args) {
let now = Date.now()
if (!previous && options.leading == false) previous = now
if (now - previous > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}
result = fn.apply(this, args)
previous = now
}
if (!timer && options.trailing) {
timer = setTimeout(() => {
previous = options.leading ? Date.now() : 0
result = fn.apply(this, args)
timer = null
}, wait);
}
return result
}
throttled.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
previous = 0
}
return throttled
}
如果需要同步结果可以使用promise,此处略过。
有什么用
节流的作用主要用于,在频繁触发某个事件的情况下,将其控制成一段时间请求一次。
- 鼠标不断点击触发(单位时间内只触发一次)
- 滚动监听,滚动到底部是否加载更多
- input输入框输入监听(节流防抖都可)
节流防抖区别
防抖和节流都是减少用户调用频率。 防抖:一段时间内,鼠标一直不停地移动,以最后一次函数执行为准(后执行)。将多次触发,变为最后一次为准。 节流:一段时间执行函数,再过一段时间在执行函数。将多次触发,变为每隔一段时间触发。