防抖
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
// 只有當點擊的時候,才回傳此函式,所以 this 是指向按鈕的
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 () {
// timer 被賦值了,直接回傳,即不執行任務
if (timer) {
return
}
// 此時 timer 沒被賦值,或 timer 已經執行完了
// 為 timer 賦值進行延時執行
timer = setTimeout(_ => {
func()
// 延遲執行以后我們要清空timer的值
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
// timer 被賦值了,直接回傳,即不執行任務
if (timer) {
return
}
// 此時 timer 沒被賦值,或 timer 已經執行完了
// 為 timer 賦值進行延時執行
timer = setTimeout(_ => {
func.apply(context, args)
// 延遲執行以后我們要清空timer的值
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))
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/303601.html
標籤:其他
