函數節流與函數防抖 最近由於處於互聯網大廠的秋招季節,因此這些天都在看前端性能優化和演算法方面的知識。在性能優化方面,看了網上的一些文章,同時看完了《高性能網站建設指南》和《高性能JavaScript》兩本書,頗有收穫,可以參看這篇文章,主要是一些前端性能優化方面的總結。傳送門: "前端性能優化最佳實 ...
函數節流與函數防抖
最近由於處於互聯網大廠的秋招季節,因此這些天都在看前端性能優化和演算法方面的知識。在性能優化方面,看了網上的一些文章,同時看完了《高性能網站建設指南》和《高性能JavaScript》兩本書,頗有收穫,可以參看這篇文章,主要是一些前端性能優化方面的總結。傳送門:前端性能優化最佳實踐
這篇文章主要是講函數節流與函數防抖相關知識的。雖然在上面兩本書裡面沒有談及這兩方面的內容,但是我覺得,對JS常用事件進行節流或者防抖的處理是屬於性能優化方面的。
目的
實現了這兩個功能函數之後發現,節流同防抖在實現過程可能不太一樣,但是目的和本質都是一樣的:
需要節流或者防抖的函數通過定時器進行間隔調用,目的是只有在執行函數的請求停止了一段時間之後才執行。
防抖和節流主要運用了時間差、閉包、定時器來處理。
相同點
節流和防抖都可以採用閉包的形式和定時器間隔調用函數的方式來實現。這主要運用了閉包的一個特性:能夠記住並訪問所在的此法作用的標識符。如果對閉包不瞭解的可以看看這個回答:什麼是閉包
函數節流
function throttle () {
var time = null
...
// 閉包
return function () {
window.setTimeout(time)
time = window.setTimeout(() => {...})
}
}
函數防抖
function debounce () {
var time = null
// 閉包
return function () {
if (!time) {
time = window.setTimeout(later, delay)
...
}
}
}
用途
個人認為,能夠使用函數節流的場景,也可以用於函數防抖。能夠使用函數防抖的場景,也適用於函數節流。
在為某個DOM節點綁定事件時,有些事件會不斷的觸發瀏覽器進行計算。如resize, mousemove, keydown, keypress, keyup, touchmove等。對於這些時間都可以使用節流或者防抖進行處理。
函數防抖
最基本的防抖實現,是沒有加入各種條件判斷的時候。當然,閱讀源碼的一個方法也是跳過各種if判斷。
function debounce (fn, option) {
var [time, context, args, result] = []
let setting = {
delay: 300 // 延遲delay秒之後間隔執行函數
}
option = Object.assign({}, setting, option)
let later = () => {
result = fn.apply(context, args)
return result
}
return function () {
args = arguments
context = this
if (!time) {
time = window.setTimeout(later, option.delay)
}
}
}
執行以上的函數會發現,只能執行一次。因此,我們需要給一個時間差,根據時間差與delay的比較,來判斷是否需要重啟定時器。
function debounce (fn, option) {
var [time, start, currStart, context, args, result] = []
let setting = {
delay: 300
}
option = Object.assign({}, setting, option)
let later = () => {
let currStart = +new Date() - start
if (option.delay > currStart) {
time = window.setTimeout(later, option.delay)
} else {
time = null
result = fn.apply(context, args)
}
}
return function () {
args = arguments
context = this
start = +new Date() // 反覆調用事件的時間
if (!time) {
time = window.setTimeout(later, option.delay)
}
}
}
如果希望第一次調用事件時就執行函數,而不是等待delay的時間。可以傳入immediate參數。
// fn需要防抖的函數, option可選
function debounce (fn, option) {
let [time, result, start, context, args] = []
let setting = {
delay: 300,
immediate: false // 第一次調用事件時是否要立即執行,預設為不立即執行
}
// setting為預設參數, 如果傳入option, 則覆蓋setting參數。
option = Object.assign({}, setting, option)
let later = () => {
let currStart = +new Date() - start
if (option.delay > currStart && currStart >= 0) {
time = window.setTimeout(later, option.delay)
} else {
time = null
result = fn.apply(context, args)
}
}
return function () {
args = arguments
context = this
start = +new Date()
if (!time) {
time = window.setTimeout(later, option.delay)
}
// 調用事件時立即執行
if (option.immediate) {
result = fn.apply(context, args)
}
return result
}
}
在用在resize事件時
function event (e) {
console.log(e)
}
let a = debounce(event, {
delay: 500,
immediate: fasle
})
window.addEventListener('resize', e => {
a(e)
})
函數節流
函數節流的實現原理與防抖大同小異。也是通過閉包和定時器來實現。但節流會在一定時間內重覆調用時,將原來的定時器清除掉。假如在500秒內重覆調用了resize時間,那麼只有在最後一次等待delay時間才會執行。
function throttle (fn, option) {
let time = null
let start = null
let setting = {
delay: 300,
mustRunTime: 500, // 在500內必須執行。如在resize事件時,按住不放超過500ms之後就必須執行函數。
immediate: false
}
option = Object.assign({}, setting, option)
return function () {
let args = arguments
let context = this
let currStart = +new Date()
if (!start) {
start = currStart
}
// 初始調用resise函數時立即執行函數,而不用等待delay的時間
if (option.immediate || currStart - start > option.mustRunTime) {
fn.apply(context, args)
option.immediate = false
start = currStart
} else {
window.clearTimeout(time)
time = window.setTimeout(() => {
fn.apply(context, args)
}, option.delay)
}
}
}
節流的調用方式與防抖相同
function event (e) {
console.log(e)
}
let a = throttle(event, {
delay: 500,
mustRunTime: false,
immediate: fasle
})
window.addEventListener('resize', e => {
a(e)
})
最後
最後想說的是,函數節流與防抖的目的都是相同的,在執行函數的請求停止了一段時間之後才執行。
但是我在節流上做了處理,如果超過一定時間就會立即執行函數。
如果需要避免某些事件的調用引出瀏覽器的多次計算,我更加傾向於使用函數節流的方式來實現。本質上,兩種方式都可以噠。