防抖与节流
本质上是优化高频率执行代码的一种手段
如:浏览器的 resize
、scroll
、keypress
、mousemove
等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率
定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
假设电梯有两种运行策略 debounce
和 throttle
,超时设定为15秒,不考虑容量限制
电梯第一个人进来后,15秒后准时运送一次,这是节流
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
实现防抖
js
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
}
}
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
}
}
该函数接受两个参数:func
是要防抖的函数,wait
是等待时间(毫秒)。
使用方法:
js
// 定义一个函数
function sayHi() {
console.log('hi');
}
// 将函数包装成防抖函数
const debouncedSayHi = debounce(sayHi, 1000);
// 调用防抖函数
debouncedSayHi();
debouncedSayHi();
debouncedSayHi();
// 定义一个函数
function sayHi() {
console.log('hi');
}
// 将函数包装成防抖函数
const debouncedSayHi = debounce(sayHi, 1000);
// 调用防抖函数
debouncedSayHi();
debouncedSayHi();
debouncedSayHi();
在这个例子中,防抖函数 debouncedSayHi
会等待 1 秒钟后才执行 sayHi
函数,如果在 1 秒内调用多次 debouncedSayHi
函数,只会执行最后一次调用。
js
function debounce(func, wait) {
let timeout
return () =>{
clearTimeout(timeout)
timeout = setTimeout(() => {
func()
}, wait)
}
}
window.onresize = debounce(() => {
console.log(123)
}, 1000)
function debounce(func, wait) {
let timeout
return () =>{
clearTimeout(timeout)
timeout = setTimeout(() => {
func()
}, wait)
}
}
window.onresize = debounce(() => {
console.log(123)
}, 1000)
js
function debounce(func, wait) {
let timeout
return function () {
let context = this // 保存this指向
let args = arguments // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
window.onresize = debounce(() => {
console.log(123)
}, 1000)
function debounce(func, wait) {
let timeout
return function () {
let context = this // 保存this指向
let args = arguments // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
window.onresize = debounce(() => {
console.log(123)
}, 1000)
js
实现节流
在规定的间隔时间内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
js
<script>
/**
* 防抖节流
* @param {*} action 回调
* @param {*} delay 等待的时间
* @param {*} context this指针
* @param {Boolean} iselapsed 是否等待上一次
* @returns {Function}
*/
function throttle(action, delay, context, iselapsed) {
let timeout = null
let lastRun = 0
return function () {
if (timeout) {
if (iselapsed) {
return
} else {
clearTimeout(timeout)
timeout = null
}
}
let elapsed = Date.now() - lastRun
let args = arguments
if (iselapsed && elapsed >= delay) {
runCallback()
} else {
timeout = setTimeout(runCallback, delay)
}
/**
* 执行回调
*/
function runCallback() {
lastRun = Date.now()
timeout = false
action.apply(context, args)
}
}
}
</script>
<script>
/**
* 防抖节流
* @param {*} action 回调
* @param {*} delay 等待的时间
* @param {*} context this指针
* @param {Boolean} iselapsed 是否等待上一次
* @returns {Function}
*/
function throttle(action, delay, context, iselapsed) {
let timeout = null
let lastRun = 0
return function () {
if (timeout) {
if (iselapsed) {
return
} else {
clearTimeout(timeout)
timeout = null
}
}
let elapsed = Date.now() - lastRun
let args = arguments
if (iselapsed && elapsed >= delay) {
runCallback()
} else {
timeout = setTimeout(runCallback, delay)
}
/**
* 执行回调
*/
function runCallback() {
lastRun = Date.now()
timeout = false
action.apply(context, args)
}
}
}
</script>
js
<script>
/**
* 防抖节流
* @param {*} action 回调
* @param {*} delay 等待的时间
* @param {*} context this指针
* @param {Boolean} iselapsed 是否等待上一次
* @returns {Function}
*/
function throttle(action, delay, context, iselapsed) {
let timeout = null
let lastRun = 0
return function () {
if (timeout) {
if (iselapsed) {
return
} else {
clearTimeout(timeout)
timeout = null
}
}
let elapsed = Date.now() - lastRun
let args = arguments
if (iselapsed && elapsed >= delay) {
runCallback()
} else {
timeout = setTimeout(runCallback, delay)
}
/**
* 执行回调
*/
function runCallback() {
lastRun = Date.now()
timeout = false
action.apply(context, args)
}
}
}
</script>
<script>
/**
* 防抖节流
* @param {*} action 回调
* @param {*} delay 等待的时间
* @param {*} context this指针
* @param {Boolean} iselapsed 是否等待上一次
* @returns {Function}
*/
function throttle(action, delay, context, iselapsed) {
let timeout = null
let lastRun = 0
return function () {
if (timeout) {
if (iselapsed) {
return
} else {
clearTimeout(timeout)
timeout = null
}
}
let elapsed = Date.now() - lastRun
let args = arguments
if (iselapsed && elapsed >= delay) {
runCallback()
} else {
timeout = setTimeout(runCallback, delay)
}
/**
* 执行回调
*/
function runCallback() {
lastRun = Date.now()
timeout = false
action.apply(context, args)
}
}
}
</script>
区别
相同点:
- 都可以通过使用
setTimeout
实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用
clearTimeout
和setTimeout
实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 - 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次
只触发最后一次事件
js
let iTime = ""
const scollThrottle = () => {
clearTimeout(iTime)
iTime = setTimeout(scroll, 100)
}
let iTime = ""
const scollThrottle = () => {
clearTimeout(iTime)
iTime = setTimeout(scroll, 100)
}
函数只执行一次
方法一,清空函数
js
let func = function () {
console.log("正常调用")
func = function () {}
}
func()
let func = function () {
console.log("正常调用")
func = function () {}
}
func()
方法二,定义全局boolen变量flag进行判断
js
let flag = true
function once() {
if (flag) {
console.log("正常调用")
flag = false
} else {
return
}
}
once()
let flag = true
function once() {
if (flag) {
console.log("正常调用")
flag = false
} else {
return
}
}
once()
应用场景
只需用户最后一次输入完,再发送请求,因此只需触发一次回调,使用防抖:
- 搜索框搜索输入。
- 手机号、邮箱验证输入检测
- 窗口大小
resize
。只需窗口调整完成后,计算窗口大小。防止重复渲染
在间隔一段时间执行一次回调,使用节流:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能