引言:
在我们的开发过程中都会遇到高频事件,比如说onscroll事件,每当页面滚动的时候,每一秒可能会触发几十次的函数执行, 假如onscroll还绑定了回调函数,并对dom进行重排,那么每一秒执行几十次dom渲染,如果dom操作过多,那么页面可能会出现卡顿的现象。
或者resize行为每一次页面窗口的变化都会绑定dom的渲染也会出现上述情况。为了优化高频事件(oninput,onkeyup,onkeydown),降低代码执行频率,采用优化方案函数的回流和防抖。
函数节流 (throttle)
函数在一定时间内执行一次核心代码。比如人在一段时间眨一次眼睛。
let btn = document.getElementById('btn')
function logger() {
console.log('logger') // 每一秒打印一次
}
function throttle(func, wait) {
let previous = 0
return function() {
let now = Date.now()
if (now - previous > wait) {
func.apply(this, arguments)
previous = now
}
}
}
btn.addEventListener('click', throttle(logger, 1000))
logger函数用来测试,正常每次点击都会打印'logger', 节流处理每一秒打印一次'logger'
previous 相对于上一次点击的时间 now 每次点击记录的事件
now - previous > wait 第一次因为previous 为 0 第一次执行 logger函数, 把previous 赋值为当前时间
第二次的时候只有当前时间和上次时间大于 wait(1000毫秒)的时候才会再次执行logger
对throttle函数优化
用户最后一次的点击事件触发不管是否小于wait这里是1s我们需要记录下来,触发一次logger
function throttle(func, wait, options) {
let previous = 0
let timer;
let me = this;
let later = function() {
previous = Date.now()
func.apply(me, arguments)
}
let throttled = function() {
let now = Date.now();
let remaining = wait - (now - previous);
if (remaining <= 0) { // 小于0 说明点击事件间隔大于1000ms
if (timer) {
clearTimeout(timer)
timer = null
}
func.apply(me, arguments)
previous = now
} else if (!timer && options.trailing) {
timer = setTimeout(later, remaining)
}
}
return throttled
}
btn.addEventListener('click', throttle(logger, 1000, { trailing: true}))
trailing等于 true开启模式
点击事件时间间隔越短并小于1s ,now-previous值越小, 那么 wait - (now - previous)值一定大于0 remaining > 0
第一次click !timer && options.trailing 成立, 结束后多执行一次later
下一次 先清除timer 再进入判断 执行下一次later
函数防抖 (debounce)
函数在执行完毕的时候,才触发核心代码执行, 比如在做电梯的时候,如果关门的过程中,有人要上来,不会走,等他上来一块走。
function logger() {
console.log('logger')
}
function debounce(func, wait) {
let timeout;
return function() {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(this, arguments)
timeout = null
}, wait)
}
}
btn.addEventListener('click', debounce(logger, 1000))
每次高频率click的时候都会清除上一次的定时器不会执行代码, 只有等到松开1s后才会执行代码
函数防抖优化
每次第一次点击的时候先执行一次
function logger() {
console.log('logger')
}
function debounce(func, wait, immediate) {
let timeout;
return function() {
clearTimeout(timeout)
if(immediate) {
let callNow = !timeout
if(callNow) func.apply(this, arguments)
}
timeout = setTimeout(() => {
func.apply(this, arguments)
timeout = null
}, wait)
}
}
btn.addEventListener('click', debounce(logger, 1000, true))