最近有遇到多次促发事件,跟页面渲染性能问题就整理了一下。
首先从滚动事件开始说起。
function showtop(){
let scrollTop = document.body.scrollTop||document.documentElement.scrollTop
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = showtop
输出如下
在绑定 scroll、resize 这类事件时,当他运行的时候,他被触发的频次非常高,如果事件中设计到大量的位置计算、DOC操作、元素重绘等工作且这些工作在下一次的scroll事件触发前之前没有完成就会造成浏览器掉帧。使得性能降低,用户体验感也差。
此类场景应用特别广泛,诸如图片懒加载、下滑自动加载数据、侧边浮动导航栏等。处理不当或者放任不管就容易引起浏览器卡死。
针对上面问题,下面介绍两种常用解决方法,防抖与节流。
防抖(debounce)
所谓防抖就是指触发事件在n秒内函数只执行一次,如果在n秒内又触发了事件,则会重新计算函数时间。
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
* immediate 是否立即执行函数
*/
function debounce(fn,delay,immediate){
let timeout;
return function (){
if(timeout){
clearTimeout(timeout)
}
timeout = null; // 时间到了,执行函数,置空 timer 避免内存泄漏
timeout = setTimeout(fn,delay);
}
}
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
//采用防抖,为显示明显时间设置为3s
window.onscroll = debounce(realFunc,3000);
//不采用防抖
window.onscroll = realFunc;
节流(Throttling)
防抖函数确实很有必要,但是也会存在一些问题,比如图片的懒加载,我希望在下滑的过程中,图片不断的被加载出来,而不是当我停止滑动的时候,图片才会被加载。
针对这一问题,我们就要求在页面在不断滚动,但是滚动handler也可以以一定的频率被触发(譬如250ms触发一次)。这个时候就要设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,短暂失效,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间),我们称它为函数的节流(Throttling)。
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(fn,delay){
let valid = true//借助闭包
return function(){
if(!valid){
// 技能冷却,暂时无法放出
return false
}
// 技能好了,可以释放
valid = false;
setTimeout(function(){
fn();
valid = true;
},delay)
}
}
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
window.onscroll = throttle(realFunc,1000);
缺点:在第一次触发的时候需要等到1s以后触发事件
在上述代码实现中,因为realFunc
本身很简单,无需考虑作用域和参数传递,所以连apply
都没有用到,实际上肯定还要考虑传递argument
以及上下文环境(毕竟apply需要用到this对象)而且在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次,所以我们修改了一版。
// “节流”函数(避免重复触发fn函数
function throttle(fn, wait = 500){
let lastTime = null
return function () {
let curTime = (new Date()).getTime() // 150000000
// 距离下次触发fn还需等待的时间(如果没有lastTime说明是第一次,可以表示执行,即等待时间为0
let remainTime = lastTime ? wait - (curTime - lastTime) : 0
console.log(remainTime <= 0 || remainTime > wait, remainTime, wait)
if (remainTime <= 0 || remainTime > wait) {
lastTime = curTime
return fn.apply(this, arguments)//arguments=[1,2]传递给(a,b)
}
}
}
// 带参数的realFunc,可以接收
function realFunc(a,b){
console.log("Success");
console.log(a,b);//1,2
}
window.onscroll = throttle(realFunc,1000)(1,2);
场景举例
说完了这两个小技巧,我们在说说平时开发过程中应用的场景。
1.搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容)。
2.页面resize事件,常见于需要做页面适配的时候,需要根据页面最终呈现的效果在进行dom渲染(这种情况一般使用防抖,因为只需要判断最后一次的变化情况)。