引言:不管是面试中还是实际应用,防抖与节流都是我们比不可少的一门技能
防抖例如?
- 假如不操作电脑十分钟后,会触发熄屏函数,然后电脑会熄屏,但是我们如果在十分钟内操作了电脑,将会重新计时十分钟
- 假如轮播图每过五秒会自动翻页,但是当我们手动点击翻页时,会重新计时五秒,防止突然跳两页的奇怪情况发生
节流例如?
- 许多网站当页面滚动到一定距离,会出现回到顶部的按钮,节流可以帮助我们减少触发滚动事件的次数
- 提交按钮的时候,仍有部分人喜欢狂点按钮,节流可以让其不管点击多少次,在一定时间内只能生效一次
防抖的经典实现
防抖函数常用于DOM事件中
function debounce(fn, delay) { //1. 点击触发的函数是debouce函数的返回值
let timeout = null
return function() { //2. 也就是这个函数
clearTimeout(timeout)
timeout = setTimeout(() => { //再次触发就重新计时
fn()
}, delay)
}
}
//测试
function fn() {
console.log('静止了1s')
}
window.onmousemove = debounce(fn, 1000)
鼠标静止1s后触发函数:
节流的经典实现
function throttle(fn, delay) {
let canRun = true
return function() {
if (!canRun) return
canRun = false
setTimeout(() => {
fn()
canRun = true
}, delay)
}
}
//测试
function fn() {
console.log('移动中...')
}
window.onmousemove = throttle(fn, 1000)
不管如何移动,函数最多每秒触发一次:
无定时器的节流
节流的核心在于如果两次触发的时间差小于规定的时间,则让第二次触发作废
所以节流有第二种实现方式:
function throttle2(fn, delay) {
let time = null
return function() {
if (!null || +new Date() - time > delay) {
fn()
time = +new Date()
}
}
}
两种节流效果相当,不过在少数的情况下有区别
- 两者确定时间的精度不一样
- new Date()依赖与本地时间,而定时器依赖于js引擎
引入this
无论何时何地,javascript的this指向永远都是一个头疼的问题,一切的根源都来自于函数可以作为参数,在防抖与节流中,也需要注意这个问题
以节流函数为例子:
function throttle(fn, delay) {
let canRun = true
return function() {
if (!canRun) return
canRun = false
setTimeout(() => {
fn()
canRun = true
}, delay)
}
}
let a = {
name: 'lihua',
sayName() {
console.log(this.name)
}
}
setInterval(throttle(a.sayName, 2000), 100)
/* 每隔2s输出一个undefined
undefined
undefined
......
*/
原因是调用该函数的是全局global对象,在浏览器中是window
为此,我们必须得传入期望调用该函数的对象:
function throttle(fn, delay) {
let canRun = true
return function(...args) {
if (!canRun) return
canRun = false
setTimeout(() => {
fn.apply(args[0]) //让a对象调用此函数
canRun = true
}, delay)
}
}
let a = {
name: 'lihua',
sayName() {
console.log(this.name)
}
}
setInterval(throttle(a.sayName, 2000), 100, a) //传入a对象 注:定时器的第三个及其之后的参数会作为回调函数的参数
/* 每隔2s输出一个lihua
lihua
lihua
......
*/
错误的实现!
在实际应用中,有部分人并没有理解防抖与节流函数的用法,例如如下的做法是错误的
function throttle(fn, delay) {
let canRun = true
return function() {
if (!canRun) return
canRun = false
setTimeout(() => {
fn()
canRun = true
}, delay)
}
}
function output() {
console.log(123)
}
setInterval(() => {
throttle(output, 2000) //需要执行的函数是throttle的返回函数,而不是throttle本身
}, 1000)