首屏加载优化

# 图片懒加载

图片懒加载是指图片出现在用户视口的时候才开始加载图片,否则不加载图片的技术。可显著减少首屏加载所需的时间。图片懒加载的关键主要有两个:

  1. 图片的src在初始化阶段是使用占位符代替。
  2. 根据图片是否处于视口区域内,将占位图替换为图片真正的src。

要实现图片的懒加载,也是按照这两个关键步骤实现,首先是img标签的src属性设置为占位符或者根本不设置src属性:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Lazy-Load</title>
  <style>
    .img {
      width: 200px;
      height:200px;
      background-color: gray;
    }
    .pic {
      // 必要的img样式
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/1.png">
    </div>
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/2.png">
    </div>
  </div>
</body>
</html>

然后在JS中根据img元素是否出现在视口位置,为img标签设置src属性:

const imgs = document.getElementsByTagName('img')
// 获取视口高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
let num = 0
function lazyLoad() {
    for (let i = 0; i < imgs.length; i++) {
        // 获取元素到视口顶部高度并且计算与视口高度的差值
        let distance = viewHeight - imgs[i].getBoundingClientRect().top
        if (distance > 0) {
            imgs[i].src = imgs[i].getAttribute('data-src')
            num = i + 1
        }
    }
}
window.addEventListener('scroll', lazyLoad, false)

# 防抖节流

上面实现图片懒加载中使用监听滚动事件的方式来调用懒加载函数,不过滚动在页面中实在是一个过于常见的事件。可能出现的情况就是用户快速滚动页面导致大量的回调函数调用,引发页面卡顿等性能问题。所以需要使用防抖节流来控制一下回调函数的触发频率。

防抖好比游戏回城,被打断了就得重新开始计时。

节流好比技能CD,用了一次之后就得等到CD好了才能再用,CD中间无论怎么按技能键都不会生效的。

上面两句话是我个人非常喜欢的对于防抖节流的解释,一个非常恰当的比喻就解释了晦涩的技术概念,下面再说一下我每个人对于防抖和节流的理解:

  1. 防抖。防抖用于控制函数触发的频率,防抖会延迟执行回调函数,防抖函数接收到函数调用请求到真正开始执行函数的这段时间内,如果又受到了同一个函数调用请求,则会重置延迟时间,重新开始计时,由此避免回调函数的频繁触发。
  2. 节流。节流使函数的触发必须间隔一段时间。一个函数在触发之后,节流会控制其下一次触发的时间和上次触发的时间的间隔,必须过了指定的时间间隔之后,相同的函数才能再次被触发,处于时间间隔之中的回调函数请求不会得到任何响应。

根据上面两个比喻和具体的概念解释,可以自己写出简单的防抖节流函数:

// 防抖函数
function debounce(fn, delay) {
    let timer = null
    return function() {
        const context = this
        const args = arguments
        // 查看用于记录延时的定时器是否还存在
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, delay)
    } 
}

// 节流函数
function throttle(fn, interval) {
    let last = 0
    return function () {
        const context = this
        const args = arguments
        // 获取当前时间
        let now = new Date()
        if (now - last > interval) {
            last = now
            fn.apply(context, args)
        }
    }
}

使用上述防抖或者节流函数包装原有的lazyLoad函数,都能有效控制其触发的时间。

目前的防抖函数是比较标准的实现,不过有时候还会要求我们将防抖节流的思想进行整合,在面试中可能会问:防抖函数简单的延迟执行函数,如果用户操作频繁的情况下会导致页面一直没有任何响应,应该如何优化?思路就是结合防抖节流,在指定事件间隔内函数被重复触发则执行防抖逻辑,超出时间间隔则执行节流逻辑。

function enhanceThrottle(fn, delay) {
    let last =0, timer = null
    return function() {
        const context = this
        const args = arguments
        let now = new Date()
        // 两次触发函数的时间小于延迟时间,走防抖逻辑
        if (now - last < delay) {
            clearTimeout(timer)
            timer = setTimeout(function() {
                // 需要重新设置一下last
                last = now
                fn.apply(context, args)
            }, delay)
        } else {
            // 函数两次触发事件已经大于延迟,必须要给用户一个反馈,走节流逻辑
            last = now
            fn.apply(context, args)
        }
    }
}
上次更新:: 2024/1/19 10:32:26