前言
一、debounce(防抖)
1.过程
- 当事件触发时,相应的函数不会立即触发,而是会等待一定的时间。
- 事件触发越频繁,函数的触发就会频繁的推迟。
- 等待了一段时间也没有事件触发,才会真正的执行响应函数。
一句话总结:距离上一次事件触发达到了约定的时间,再执行响应函数。
2.应用场景
- 输入框中频繁的输入内容,搜索或者提交信息。
- 频繁的点击按钮,触发某个事件。
- 监听浏览器滚动事件,完成某些特定的操作。
- 用户缩放浏览器的
resize事件。
注:项目中使用防抖是性能优化的方案之一。
3.实现防抖
3.1 基本实现(核心)
思考过程:
1. 需要接收什么参数?
- 参数1:回调函数;
- 参数2:延迟时间(`delay`)。
2. 有什么返回值?
- 返回一个函数。
3. 内部怎么实现?
- 延时操作需要使用`setTimeout`函数。
- 在执行响应函数前,需要取消前一次的预执行响应函数。
实现:
// 防抖的核心
function debounce (fn, delay) {
// 1.用于记录上一次事件触发的timer
let timer = null
// 2.触发事件时执行的函数
const _debounce = () => {
// 2.1.如果有再次触发事件(更多次触发)事件,取消上一次事件
timer && clearTimeout(timer)
// 2.2.延迟执行对应的fn函数(传入的回调函数)
timer = setTimeout(() => {
fn()
// 执行函数之后,timer初始化
timer = null
}, delay)
}
return _debounce
}
涉及知识点:
- `setTimeout`的使用
- 闭包
3.2 优化1:this和参数绑定
function debounce (fn, delay) {
let timer = null
// 参数传递,不能使用箭头函数
const _debounce = function (...args) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
// 新增:绑定this,传递参数
fn.apply(this, args)
timer = null
}, delay)
}
return _debounce
}
涉及知识点:
- `apply`的使用
- 函数的参数`arguments`
3.3 优化2:取消功能实现
function debounce (fn, delay) {
let timer = null
const _debounce = function (...args) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
// 新增:给_debounce函数绑定一个取消的函数
_debounce.cancel = function () {
timer && clearTimeout(timer)
}
return _debounce
}
涉及知识点:
- 在不改变返回值的形式下增加返回内容(向后兼容)
3.4 优化3:立即执行功能实现
// 原则:一个函数只做一件事,一个变量只用于记录一个状态
// 新增:新增立即执行标识参数,默认为false
function debounce (fn, delay, immediate = false) {
let timer = null
// 新增:使用另一个变量用于记录执行状态
let isInvoke = false
const _debounce = function (...args) {
timer && clearTimeout(timer)
// 新增:立即执行
if (immediate && !isInvoke) {
fn.apply(this, args)
isInvoke = true
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
// 新增:状态初始化
isInvoke = false
}, delay)
}
_debounce.cancel = function () {
timer && clearTimeout(timer)
// 新增:状态初始化
timer = null
isInvoke = false
}
return _debounce
}
优化初始化模块:
function debounce (fn, delay, immediate = false) {
let timer = null
let isInvoke = false
// 优化:封装初始化模块
const initData = function () {
timer = null
isInvoke = false
}
const _debounce = function (...args) {
timer && clearTimeout(timer)
if (immediate && !isInvoke) {
fn.apply(this, args)
isInvoke = true
return
}
timer = setTimeout(() => {
fn.apply(this, args)
initData()
}, delay)
}
_debounce.cancel = function () {
timer && clearTimeout(timer)
initData()
}
return _debounce
}
涉及知识点:
- 一个函数只做一件事,一个变量只用于记录一个状态
3.5 优化4:获取返回值实现
function debounce (fn, delay, immediate = false) {
let timer = null
let isInvoke = false
const initData = function () {
timer = null
isInvoke = false
}
const _debounce = function (...args) {
// 新增:返回promise
return new Promise((resolve, reject) => {
// 新增:使用try/catch
try {
timer && clearTimeout(timer)
// 新增:变量保存执行结果
let res = null
if (immediate && !isInvoke) {
res = fn.apply(this, args)
// 新增:返回结果
resolve(res)
isInvoke = true
return
}
timer = setTimeout(() => {
res = fn.apply(this, args)
// 新增:返回结果
resolve(res)
initData()
}, delay)
} catch (error) {
reject(error)
}
})
}
_debounce.cancel = function () {
timer && clearTimeout(timer)
initData()
}
return _debounce
}
涉及知识点:
- `Promise`
- `try/catch`
二、throttle(节流)
1.过程
- 当事件触发时,会执行这个事件的响应函数。
- 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数。
- 不管在这个中间有多少次触发这个事件,执行函数的频率总是固定的。
一句话总结:从事件触发开始,一定时间内只执行一次响应函数。
2.应用场景
- 监听页面的滚动事件。
- 鼠标移动事件。
- 用户频繁点击操作按钮。
- 游戏中的一些设计,如王者荣耀的攻击动作。
3.实现节流
3.1 基本实现(核心)
思考过程:
1. 需要接收什么参数?
- 参数1:回调函数;
- 参数2:间隔时间(`interval`)。
2. 有什么返回值?
- 返回一个函数。
3. 内部怎么实现?
- 满足公式:waitTime = interval - (nowTime - startTime)
- 满足条件:if (waitTime <= 0) {
- fn()
- starTime = nowTime
- }
实现:
// 节流的核心
function throttle (fn, interval) {
let startTime = 0
const _throttle = function () {
// 1.获取当前时间
const nowTime = new Date().getTime()
// 2.计算需要等待的时间执行函数
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
fn()
startTime = nowTime
}
}
return _throttle
}
涉及知识点:
- 闭包
3.2 优化1:this和参数绑定
function throttle (fn, interval) {
let startTime = 0
// 新增:参数传递,不能使用箭头函数
const _throttle = function (...args) {
const nowTime = new Date().getTime()
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
// 新增:绑定this,传递参数
fn.apply(this, args)
startTime = nowTime
}
}
return _throttle
}
涉及知识点:
- `apply`的使用
- 函数的参数`arguments`
3.3 优化1:立即执行的控制
可以设置首次是否立即执行。默认是执行。
// 新增:新增立即执行标识参数leading,默认为true
function throttle (fn, interval, leading = true) {
let startTime = 0
const _throttle = function (...args) {
const nowTime = new Date().getTime()
// 新增:立即执行的控制
if (!leading && (startTime === 0)) {
startTime = nowTime
}
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
fn.apply(this, args)
startTime = nowTime
}
}
return _throttle
}
3.4 优化2:尾部执行的控制
常规实现是立即执行和尾部不执行。
待更新......有点复杂
3.5 优化2:取消功能实现
待更新......和尾部执行有关
3.6 优化3:获取返回值实现
function throttle (fn, interval, leading = true) {
let startTime = 0
const _throttle = function (...args) {
// 新增:返回Promise
return new Promise((resolve, reject) => {
try {
const nowTime = new Date().getTime()
if (!leading && (startTime === 0)) {
startTime = nowTime
}
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
const res = fn.apply(this, args)
// 新增:返回
resolve(res)
startTime = nowTime
}
} catch (error) {
reject(error)
}
})
}
return _throttle
}
涉及知识点:
- `Promise`
- `try/catch`