说白话:
节流就像我们喝水上厕所一样... Emm咱们还是换个例子吧。。。
比如说吃饭。吃饭说明规定,五个小时吃一次。吃了一次饭,小狗蹦蹦哒哒地玩了五个小时。五个小时一到, 小狗再回来吃饭。依次类推,每五小时回来吃一次饭。
说人话:
定义:如果持续触发事件,单位时间内执行一次函数。
节流模样:
<!-- <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>
我们可以直接使用lodash.js或者underscore.js中的节流函数,查看节流的效果。我的鼠标一直在div中移动,节流就会每个一段时间打印一次。
扒开面具见真相
对于我们而言,光知其然,是远远不够的;我们更要知其所以然!
老样子,咱们给自己上一课吧!
对于节流函数,与防抖的形参类似。它有三个参数:节流的执行函数fn;需要延迟的毫秒数wait;第三个参数options禁用第一次首先执行可以传递{leading: false}
,禁用最后一次执行{trailing: false}
。
那么根据第三个,可以传递两个属性参数。可能会出现三种情况:
- 第一次先执行,最后一次不执行
- 第一次不执行,最后一次执行
- 第一次先执行,最后一次也执行
有头的
第一次先执行。
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函数。这样就能够保证第一次触发就能够先执行。
但是一定要将记录当前时间点的值赋给默认时间点,不然,鼠标移动时会一直触发函数执行。
有尾巴的
在时间段尾部执行。
function throttle(fn, wait) { let timer = null return function (...args) { if (timer) return timer = setTimeout(() => { fn.apply(this, args) timer = null }, wait) } }
考虑在尾部执行的情况。我们可以联想到定时器setTimeout
,每隔一段时间触发执行一次。如果刚进来的话,正好在上一次执行的时间间隔内,就直接返回。只有当达到预定时间时(没有定时器,刚好要执行),执行函数,但是执行完成后一定要记得清空定时器,以免耽误下一次事件触发。
有头又有尾
嘿,这部就像我们做人一样嘛,有始有终!
根据第三个参数进行判断头尾。再把时间戳版和定时器版两者东拼拼西凑凑,大体模型就出来拉。
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)。
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输入框输入监听(节流防抖都可)
节流防抖区别
防抖和节流都是减少用户调用频率。
防抖:一段时间内,鼠标一直不停地移动,以最后一次函数执行为准(后执行)。将多次触发,变为最后一次为准。
节流:一段时间执行函数,再过一段时间在执行函数。将多次触发,变为每隔一段时间触发。
赠言
做人如果没有梦想,那跟咸鱼有什么区别啊!