防抖
JavaScript 防抖 - Web前端工程师面试题讲解
🌰 自动门感应到有人,打开门,并且开始5秒倒计时,在 5 s 内 有另外一个人靠近到门,门感应到人,?? 重新5秒倒计时
当事件被触发时,设定一个延迟 ,若期间事件又被触发,则重新设定延迟 ,直到延迟结束,执行动作 (防止多次触发)
web 应用上面
- 改变页面大小的统计
- 滚动页面位置的统计
- 输入框连续输入的请求次数控制
一开始,点击按钮,console.log('pay money')
<body>
<button id="btn">click</button>
</body>
<script>
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
}
btn.addEventListener('click', payMoney)
</script>
定义 debounce
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
}
function debounce(func) {
return function () {
func()
}
}
btn.addEventListener('click', debounce(payMoney))
设置延时
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
}
function debounce(func, delay) {
return function () {
setTimeout(_ => func(), delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
清除延时:?? 未能执行
原因
每次点击的时候就会执行返回函数里面的内容
每次点击的执行函数都是独立的,互不干涉
正因为他们之间没有联系,清除延时在这里完全没有起作用
要让这些独立的执行函数之间有联系,需要用到作用域链(闭包)
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
}
function debounce(func, delay) {
return function () {
let timer
clearInterval(timer)
timer = setTimeout(_ => func(), delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
🤔
将 timer 放在返回函数的外围,这样在定义监听事件的同时,就定义了 timer 变量
因为作用域链,所有独立的执行函数都能访问到这个timer变量
而且现在这个timer变量只创建了一次。是唯一的,我们只不过不断给timer赋值进行延时而已
每个清除延时就是清除上一个定义的延时,相当于多个函数共用同一个外部变量
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
}
function debounce(func, delay) {
let timer
return function () {
clearInterval(timer)
timer = setTimeout(_ => func(), delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
此时的代码,this 是指向 window ?
因为回调的原因,运行时已经在Window下了
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
console.log(this);
}
function debounce(func, delay) {
let timer
return function () {
clearInterval(timer)
timer = setTimeout(_ => func(), delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
解决办法 🤔
在 setTimeout 之前将 this 保存下来,此时的 this 是指向按钮的
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
console.log(this);
}
function debounce(func, delay) {
let timer
return function () {
let context = this
clearInterval(timer)
timer = setTimeout(_ => {
func.apply(context)
}, delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
考虑参数的问题,添加 arg
const btn = document.getElementById('btn')
function payMoney() {
console.log('pay money');
console.log(this);
}
function debounce(func, delay) {
let timer
return function () {
let context = this
let args = arguments
clearInterval(timer)
timer = setTimeout(_ => {
func.apply(context)
console.log(context, args);
}, delay)
}
}
btn.addEventListener('click', debounce(payMoney, 1000))
节流
先触发一次后,防止接下来多次触发
滚动屏幕:统计用户滚动屏幕的行为来作出相应的网页反应
当用户不断进行滚动,就会不断产生请求,相应也会不断增加,容易导致? ?? 网络阻塞
我们就可以在触发事件的时候就马上执行任务,然后设定时间间隔限制 ,在这段时间内不管用户如何进行滚动都忽视操作
在时间到了以后如果监测到用户有滚动行为,再次执行任务。并且设置时间闻隔
首先,写个改变页面尺寸的同时改变页面背景颜色的代码
function coloring() {
let r = Math.floor(Math.random() * 255)
let g = Math.floor(Math.random() * 255)
let b = Math.floor(Math.random() * 255)
document.body.style.background = `rgb(${r}, ${g}, ${b})`
}
window.addEventListener('resize', coloring)
function throttle(func, delay) {
let timer
return function () {
timer = setTimeout(_ => {
func()
}, delay)
}
}
window.addEventListener('resize', throttle(coloring, 2000))
判断触发的事件是否在时间间隔内
function throttle(func, delay) {
let timer
return function () {
if (timer) {
return
}
timer = setTimeout(_ => {
func()
timer = null
}, delay)
}
}
window.addEventListener('resize', throttle(coloring, 2000))
解决 this 指向(虽然当前的这个例子就是在 Window 下的)
function throttle(func, delay) {
let timer
return function () {
let context = this
let args = arguments
if (timer) {
return
}
timer = setTimeout(_ => {
func.apply(context, args)
timer = null
}, delay)
}
}
window.addEventListener('resize', throttle(coloring, 1000))
节流核心:事件间隔 另一种常见的时间间隔就是用Date对象
function throttle(func, delay) {
let pre = 0
return function () {
let now = new Date()
if (now - pre > delay) {
func()
pre = now
}
}
}
window.addEventListener('resize', throttle(coloring, 1000))
解决参数问题
function throttle(func, delay) {
let pre = 0
return function () {
let context = this
let args = arguments
let now = new Date()
if (now - pre > delay) {
func.apply(context, args)
pre = now
}
}
}
window.addEventListener('resize', throttle(coloring, 1000))
|