节流、防抖
前端有一些骚气的名词:节流、防抖、闭包、原型链、柯里化、Event Loop、渐进升级、优雅降级…,拽拽的不明觉厉,大概都是蛮荒时代遗留下来的,大神的眼里这些都是行话,小菜的眼里这些都是**,是故弄玄虚还是奇技淫巧,关键是看能不能理解原理和应用场景然后举一反三触类旁通,下面就来扒掉节流和防抖的底裤
节流和防抖的目的都是避免某一特定方法频繁执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="text" id='input'>
<button type="submit" id='btn'>搜索</button>
<script>
const eInput = document.getElementById('input')
const eBtn = document.getElementById('btn')
</script>
<script>
eInput.addEventListener('keyup', onInputKeyUp)
eBtn.addEventListener('click', onSearch)
function doSearch(params) {
console.log(Date.now(), params)
}
function onInputKeyUp(event) {
let query = event.target.value
doSearch(query)
}
function onSearch(params) {
let query = eInput.value
doSearch(query)
}
</script>
</body>
</html
防抖
场景
- 场景一:如图,连续点击搜索按钮,每一次都会触发doSearch执行一次接口调用,给服务器造成了性能压力
- 场景二:假设一个页面上有很多个tab,每一tab上都有一个table,分别对应不同条件的结果,如果频繁切换tab,每切换一次就会执行一次接口查询,有可能接口较慢,数据还没渲染完就切换到了另一个tab,如此对服务器造成压力,也对前端渲染造成了影响
防抖的作用就是延时执行特定方法,可以利用settimeout实现
实现
function debounce(fn, delay = 0) {
let timer = null
return function (argvs) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
fn(argvs)
}, delay * 1000);
}
}
eBtn.addEventListener('click', debounce(onSearch, 3))
- 闭包封存了记录定时器的timer变量
- 每次触发事件都会重置timer,然后将新的定时器放入任务队列,并重新计时
- 如果没有新的事件触发,定时器任务的回调方法会在延时delay后执行
防抖带来的问题
防抖处理后,特定的方法触发后delay时间段内最多执行一次,如果事件不断的触发,那么该特定的方法永远都不会执行,有时候这样也不符合需求,我们可能希望在事件不断触发的同时要在特殊的节点或者隔一段时间都会执行一次这个方法
节流
场景
实现
事件间隔
function throttle1(fn, delay = 0) {
let lastExec = null
return function (argvs) {
let now = Date.now()
lastExec = lastExec || now
if (now - lastExec >= delay * 1000) {
fn(argvs)
lastExec = now
}
}
}
eInput.addEventListener('keyup', throttle1(onInputKeyUp, 3))
- 闭包封存上一次执行的时间戳lastExec
- 触发时检查当前时间与上次执行的时间的时间间隔
- 时间间隔大于设定值,则执行一次特定方法,并更新上次执行时间为当前时间
延时器
function throttle2(fn, delay = 0) {
let hasExec = false
return function (argvs) {
if (hasExec) return
hasExec = true
setTimeout(() => {
fn(argvs)
hasExec = false
}, delay * 1000);
}
}
eInput.addEventListener('keyup', throttle1(onInputKeyUp, 3))
- 闭包封存记录特定方法执行与否的变量hasExec
- 触发时检查执行状态,如未执行则更新执行状态为true,并开启定时器
- 定时任务执行时,执行特定的方法,并重置执行状态为false
对比
- 相同:都是限制回调函数调用频率
- 不同:
- 防抖:一段时间内,回调函数只会调用一次,即触发事件的最后一次
- 节流:回调函数每隔一段时间调用一次
应用
- 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
- 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)