前端性能优化

789 阅读5分钟

CDN优化

将CDN的域名与主域名区分开来,由于cookie会不分青红皂白的跟随域名,而CDN主要是请求静态资源,根本不需要验证信息。通过区分域名,就可以大大提升效率

服务端渲染

服务端渲染主要解决首屏加载体验问题和SEO问题。即服务器直接返回dom树,但是浏览器客户端很多,但是服务器很少,还是尽量少用服务端渲染

基于渲染流程的 CSS 优化建议

  • css解析是从右往左的
  • 避免使用通配符,只对需要用到的元素进行选择。
  • 关注可以通过继承实现的属性,避免重复匹配重复定义。
  • 少用标签选择器。如果可以,用类选择器替代
错误示范

#myList li{}

课代表

.myList_li {}
  • 不要画蛇添足,id 和 class 选择器不应该被多余的标签选择器拖后腿
  • 减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素

CSS 与 JS 的加载顺序优化

HTML、CSS 和 JS,都具有阻塞渲染的特性。

CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间

现在很多团队都已经做到了尽早(将 CSS 放在 head 标签里)和尽快(启用 CDN 实现静态资源加载速度的优化)

js引擎独立于渲染引擎

js会抢走html和css的控制权

js加载的三种方式

  • 正常模式
<script src="index.js"></script>

这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。
  • async模式
<script async src="index.js"></script>

async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
  • defer模式
<script defer src="index.js"></script>

defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。

从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。

回流与重绘

  • 回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
  • 重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

重绘不一定导致回流,回流一定会导致重绘

给DOM提速

减少 DOM 操作:少交“过路费”、避免过度渲染


// 请求10000次,更改10000次
for(var count=0;count<10000;count++){ 
  document.getElementById('container').innerHTML+='<span>我是一个小测试</span>'
} 

// 只获取一次container
let container = document.getElementById('container')
for(let count=0;count<10000;count++){ 
  container.innerHTML += '<span>我是一个小测试</span>'
} 

// 只有一次
let container = document.getElementById('container')
let content = ''
for(let count=0;count<10000;count++){ 
  // 先对内容进行操作
  content += '<span>我是一个小测试</span>'
} 
// 内容处理好了,最后再触发DOM的更改
container.innerHTML = content

可以使用DOM Fragment 可以以操作DOM的模式操作容器

如何规避回流与重绘

  • 将“导火索”缓存起来,避免频繁改动
  • 避免逐条改变样式,使用类名去合并样式
  • 将 DOM “离线”

一旦我们给元素设置 display: none,将其从页面上“拿掉”,那么我们的后续操作,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线化。

const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)

离线化后就是这样:

let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
container.style.display = 'block'

浏览器有一个flush任务器,可以聪明的将回流与重绘的任务整合后统一触发,但是遇到即时的触发回流的操作时,就会立即触发

当你要用到像这样的属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 时,你就要注意了!

“像这样”的属性,到底是像什么样?——这些值有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。

优化首屏体验——Lazy-Load 初探

在懒加载的实现中,有两个关键的数值:一个是当前可视区域的高度,另一个是元素距离可视区域顶部的高度

<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>

懒加载最好用节流和防抖优化

仅用于个人整理