文章目录
概要
防抖与节流在前端中是必须要掌握的知识,如果你还不会的话,那我们就嘴对嘴,哦,不不不!那我们就手拉手一起实现一下吧!
防抖
啥子是防抖呢? 就是触发了一个事件,但是这个事件对应的函数并不会立即执行,而是会等待固定的时间t后才会执行。如果在t时间内再次触发了这个事件,则需要取消上次的函数执行,再次等待t时间后执行。具体流程如下:
应用场景
1.输入框中频繁的提交内容,搜索,提交信息
2.频繁的点击按钮,触发某个事件
3.监听浏览器滚动事件,完成特定操作
4.用户缩放浏览器的resize事件
1. 防抖的基本功能实现
function debounce(fn, delay) {
// 1.标志位,用来记录上一次事件触发的timer
let timer = null
/*
为了使小白第一次看这个也能看懂,这里解释的再详细一点。当第一次调用时,timer为null,
因此Boolean(timer) = false,所以clearTimeout(timer)不会执行,即函数不会取消执行,
而是等待delay时间片后执行。
但是如果在这段时间内再次触发了debounce,则取消上次函数的执行,重新等待delay后执行。
不知道你有没有这样的疑问,在一个delay时间片内再次触发,setTimeout()不是还没出结果,
那么timer不是还是null吗,那么clearTimeout不会执行啊,如何取消上次的执行呢?
这里如果有疑问的话,说明你的JS还有待提高哦。
首先,setTimeout确实是负责延迟执行函数的,但他延迟执行的是传入的回调函数,而不是自身。
其次,setTimeout由window负责调用,在代码执行过程中会被加入到宏任务队列中,
在微任务执行完过后,就开始依次执行宏任务。当执行到它时,会立即执行并返回一个timeoutId。
因此,在delay时间片内再次触发时,timer已经有值了,所以就可以取消上次的函数执行了。
*/
// 2.触发事件执行的函数
const myDebounce = () => {
// 若等待时间内再次调用,则取消函数执行
if(timer) clearTimeout(timer)
// 否则等待delay时间后执行函数
timer = setTimeout(() => {
fn()
timer = null
}, delay)
}
return myDebounce
}
2. 关于this和参数绑定的优化
function debounce(fn, delay) {
// 1.标志位,用来记录上一次事件触发的timer
let timer = null
// 2.触发事件执行的函数
const myDebounce = (...args) => {
// 若等待时间内再次调用,则取消函数执行
if(timer) clearTimeout(timer)
// 否则等待delay时间后执行函数
timer = setTimeout(() => {
fn.call(this, ...args)
// 或 fn.apply(this, args)
timer = null
}, delay)
}
return myDebounce
}
3. 关于取消功能的实现
function debounce(fn, delay) {
// 1.标志位,用来记录上一次事件触发的timer
let timer = null
// 2.触发事件执行的函数
const myDebounce = (...args) => {
// 若等待时间内再次调用,则取消函数执行
if(timer) clearTimeout(timer)
// 否则等待delay时间后执行函数
timer = setTimeout(() => {
fn.call(this, ...args)
// 或 fn.apply(this, args)
timer = null
}, delay)
}
// 3.给myDebounce绑定一个取消的函数
myDebounce.cancle = () => {
if(timer) clearTimeout(timer)
}
return myDebounce
}
4. 关于立即执行功能的实现
// 原则:一个函数用来执行一件事,一个变量用来记录一种状态
function debounce(fn, delay, immediate = false) {
// 1.标志位,用来记录上一次事件触发的timer
let timer = null
// 用来控制防止一个时间片内多次触发而多次执行
let isInvoke = false
// 2.触发事件执行的函数
const myDebounce = (...args) => {
// 若等待时间内再次调用,则取消函数执行
if(timer) clearTimeout(timer)
// 判断是否需要立即执行
if(immediate && !isInvoke) { // 如果不需要延迟,立即执行
fn.apply(this, args)
isInvoke = true
return
} else { // 延迟执行
// 否则等待delay时间后执行函数
timer = setTimeout(() => {
fn.call(this, ...args)
// 或 fn.apply(this, args)
timer = null
isInvoke = false
}, delay)
}
}
// 3.给myDebounce绑定一个取消的函数
myDebounce.cancle = () => {
if(timer) clearTimeout(timer)
timer = null
isInvoke = false
}
return myDebounce
}
5. 关于获取返回值功能的实现
// 原则:一个函数用来执行一件事,一个变量用来记录一种状态
function debounce(fn, delay, immediate = false, resultCallback) {
// 1.标志位,用来记录上一次事件触发的timer
let timer = null
// 用来控制防止一个时间片内多次触发而多次执行
let isInvoke = false
// 2.触发事件执行的函数
const myDebounce = function(...args) {
return new Promise((resolve, reject) => {
try {
// 若等待时间内再次调用,则取消函数执行
if(timer) clearTimeout(timer)
// 判断是否需要立即执行
if(immediate && !isInvoke) { // 如果不需要延迟,立即执行
const res = fn.apply(this, args)
if(resultCallback) resultCallback(res)
resolve(res)
isInvoke = true
return
} else { // 延迟执行
// 等待delay时间后执行函数
timer = setTimeout(() => {
const res = fn.call(this, ...args)
// 或 fn.apply(this, args)
if(resultCallback) resultCallback(res)
resolve(res)
timer = null
isInvoke = false
}, delay)
}
} catch(error) {
reject(error)
}
})
}
// 3.给myDebounce绑定一个取消的函数
myDebounce.cancle = () => {
if(timer) clearTimeout(timer)
timer = null
isInvoke = false
}
return myDebounce
}
节流
那啥子是节流呢?就是在一个时间片内无论触发多少次事件,该事件也只会执行一次。具体流程如下:
应用场景
1.监听页面的滚动事件
2.鼠标移动事件
3.用户频繁点击按钮操作
4.游戏中的一些设计
1. 基本功能实现
function throttle(fn, interval) {
// 1.记录上一次的开始时间
let lastTime = 0
// 2.事件触发时, 真正执行的函数
const myThrottle = function() {
// 2.1.获取当前事件触发时的时间
const nowTime = new Date().getTime()
// 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
// 2.3.真正触发函数
fn()
// 2.4.保留上次触发的时间
lastTime = nowTime
}
}
return myThrottle
}
2. 关于this和参数绑定的实现
function throttle(fn, interval) {
// 1.记录上一次的开始时间
let lastTime = 0
// 2.事件触发时, 真正执行的函数
const myThrottle = function(...args) {
// 2.1.获取当前事件触发时的时间
const nowTime = new Date().getTime()
// 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
// 2.3.真正触发函数
fn.apply(this, args)
// 2.4.保留上次触发的时间
lastTime = nowTime
}
}
return myThrottle
}
3. 关于立即执行功能的实现
function throttle(fn, interval, options = { leading: true }) {
// 1.记录上一次的开始时间
let lastTime = 0
const { leading } = options
// 2.事件触发时, 真正执行的函数
const myThrottle = function(...args) {
// 2.1.获取当前事件触发时的时间
const nowTime = new Date().getTime()
// 对立即执行功能进行控制
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 myThrottle
}
4. 关于尾调用执行功能的实现
function throttle(fn, interval, options = { leading: true, trailing: false }) {
// 1.记录上一次的开始时间
let lastTime = 0
const { leading, trailing } = options
let timer = null
// 2.事件触发时, 真正执行的函数
const myThrottle = function() {
// 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(() => {
timer = null
lastTime = !leading ? 0: new Date().getTime()
fn.apply(this, args)
}, remainTime)
}
}
return myThrottle
}
5. 关于取消执行功能的实现
function throttle(fn, interval, options = { leading: true, trailing: false }) {
// 1.记录上一次的开始时间
let lastTime = 0
const { leading, trailing } = options
let timer = null
// 2.事件触发时, 真正执行的函数
const myThrottle = 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(() => {
timer = null
lastTime = !leading ? 0: new Date().getTime()
fn.apply(this, args)
}, remainTime)
}
}
myThrottle.cancel = function() {
if(timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return myThrottle
}
6. 关于获取函数返回值功能的实现
function throttle(fn, interval, options = { leading: true, trailing: false }) {
// 1.记录上一次的开始时间
const { leading, trailing, resultCallback } = options
let lastTime = 0
let timer = null
// 2.事件触发时, 真正执行的函数
const myThrottle = function(...args) {
return new Promise((resolve, reject) => {
// 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) resultCallback(result)
resolve(result)
// 2.4.保留上次触发的时间
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
lastTime = !leading ? 0: new Date().getTime()
const result = fn.apply(this, args)
if (resultCallback) resultCallback(result)
resolve(result)
}, remainTime)
}
})
}
myThrottle.cancel = function() {
if(timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return myThrottle
}