建议购买原文,前端性能优化原理与实践,本文学习总结用。如有侵权,感谢联系,删除。
1、应用篇
1)、优化首屏体验——Lazy-Load 初探
先加载首屏的图片资源,至于下面的图片,可以等用户下拉的瞬间再即时去请求 ——这个延迟加载的过程,就是 Lazy-Load。
懒加载的实现中,有两个关键的数值:
- 一个是当前可视区域的高度
const viewHeight = window.innerHeight||document.documentElement.clientHeight - 另一个是元素距离可视区域顶部的高度
getBoundingClientRect() 方法来获取返回元素的 大小及其相对于视口的位置
懒加载的实现(重点)
<style>
.img {
width: 200px;
height:200px;
background-color: gray;
}
.pic {
// 必要的img样式
}
</style>
<div class="img">
// 注意我们并没有为它引⼊入真实的src
<img class="pic" alt="加载中" data-src="./images/1.png">
</div>
<div class="img">
<img class="pic" alt="加载中" data-src="./images/2.png">
</div>
<div class="img">
<img class="pic" alt="加载中" data-src="./images/3.png">
</div>
<div class="img">
<img class="pic" alt="加载中" data-src="./images/4.png">
</div>
<div class="img">
<img class="pic" alt="加载中" data-src="./images/5.png">
</div>
<script>
// 获取所有的图⽚片标签
const imgs = document.getElementsByTagName('img')
// 获取可视区域的⾼高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
// num⽤用于统计当前显示到了了哪⼀一张图⽚片,避免每次都从第⼀一张图⽚片开始检查是否露露出
let num = 0
function lazyload(){
for(let i=num; i<imgs.length; i++) {
// ⽤用可视区域⾼高度减去元素顶部距离可视区域顶部的⾼高度
let distance = viewHeight - imgs[i].getBoundingClientRect().top
// 如果可视区域⾼高度⼤大于等于元素顶部距离可视区域顶部的⾼高度,说明元素露露出
if(distance >= 0 ){
// 给元素写⼊入真实的src,展示图⽚片
imgs[i].src = imgs[i].getAttribute('data-src')
// 前i张图⽚片已经加载完毕,下次从第i+1张开始检查是否露露出
num = i + 1
}
}
}
// 监听Scroll事件
window.addEventListener('scroll', lazyload, false);
</script>
scroll 事件,是一个危险的事件——它太容易被触发了,需添加节流防抖,优化性能,添加代码,看下面
2)、事件的节流(throttle)与防抖(debounce)
scroll 事件,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)非常容易被反复触发的事件。
危害: 频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。
本质:
- 这两个东西都以闭包的形式存在。
- 它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。
1、节流:第一个说了算
是通过在一段时间内无视后来产生的回调请求来实现的
实现
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
// last为上⼀一次触发回调的时间
let last = 0
// 将throttle处理理结果当作函数返回
return function () {
// 保留留调⽤用时的this上下⽂文
let context = this
// 保留留调⽤用时传⼊入的参数
let args = arguments
// 记录本次触发回调的时间
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否⼩小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔⼤大于我们设定的时间间隔阈值,则执⾏行行回调
last = now;
fn.apply(context, args);
}
} }
// ⽤用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
2、防抖:最后一个说了算
// fn是我们需要包装的事件回调, delay是每次推迟执⾏行行的等待时间
function debounce(fn, delay) {
// 定时器器
let timer = null
// 将debounce处理理结果当作函数返回
return function () {
// 保留留调⽤用时的this上下⽂文
let context = this
// 保留留调⽤用时传⼊入的参数
let args = arguments
// 每次事件被触发时,都去清除之前的旧定时器器
if(timer) {
clearTimeout(timer)
}
// 设⽴立新定时器器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// ⽤用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
用 Throttle 来优化 Debounce
debounce 的问题
debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产 生“这个页面卡死了”的观感。
解决
为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以, 但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须 要给用户一个响应。
// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {
// last为上⼀一次触发回调的时间, timer是定时器器
let last = 0, timer = null
// 将throttle处理理结果当作函数返回
return function () {
// 保留留调⽤用时的this上下⽂文
let context = this
// 保留留调⽤用时传⼊入的参数
let args = arguments
// 记录本次触发回调的时间
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否⼩小于时间间隔的阈值
if (now - last < delay) {
// 如果时间间隔⼩小于我们设定的时间间隔阈值,则为本次触发操作设⽴立⼀一个新的定时器器
clearTimeout(timer)
timer = setTimeout(function () {
last = now
fn.apply(context, args)
}, delay)
} else {
// 如果时间间隔超出了了我们设定的时间间隔阈值,那就不不等了了,⽆无论如何要反馈给⽤用户⼀一次响应
last = now
fn.apply(context, args)
}
}
}
// ⽤用新的throttle包装scroll的回调
const better_scroll = throttle(() => console.log('触发了了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)