性能优化

加载相关

为什么要强调CSS要放在header里,js放在尾部?

DOMContentLoaded 和 load

  • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片...
  • load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成

构建Render树需要DOM和CSSOM,所以HTML和CSS都会阻塞渲染。所以需要让CSS尽早加载(如:放在头部),以缩短首次渲染的时间

除此之外,由于CSS不会阻塞文档的解析,但是会阻塞文档渲染。把CSS放在头部可以先生成CSSOM树,后续渲染DOM的时候,可以一次性构建Render树,只需要渲染一次;如果把CSS放在后面,会先解析一次DOM,加载CSS之后,会重新渲染之前的DOM,需要两次渲染。

阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML

普通的脚本会阻塞浏览器解析,加上defer或async属性,脚本就变成异步,可等到解析完毕再执行

  • async异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在onload前,但不确定在DOMContentLoaded事件的前后
  • defer延迟执行,相当于放在body最后(理论上在DOMContentLoaded事件前)
  • 执行js代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑用Web Worker,它可以让我们另开一个线程执行脚本,不影响渲染

Onload&DOMContentLoaded&domready

  • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片
  • load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成
  • domready事件在DOM加载后、资源加载之前被触发,在本地浏览器的DOMContentLoaded事件的形式被调用。

白屏、首屏

白屏

白屏时间指的是浏览器开始显示内容的时间,一般认为浏览器开始渲染body或者解析完head标签的时候就是页面白屏结束的时间。

计算方法:IE8-: title后输出一个时间pagestartime;

head结束前 输出一个时间firstpaint。

白屏时间=firstpaint-pagestarttime/performance.timing.navigationStart;

优化

1.加快js的执行速度,比如无限滚动的页面,可以用js先渲染一个屏幕范围内的东西

2.减少文件体积

3.首屏同步渲染html,后续的滚屏再异步加载和渲染。

首屏

首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。

计算方法:

1.模块标签标记。适用于内容不需要拉取数据才能生存以及页面不考虑图片等资源的加载情况。结束位置加时间戳输出时间。

2.统计首屏内图片加载最慢事件。 通常图片加载最慢,所以会把首屏内加载事件最慢的图片时间。

3.自定义计算

优化

首屏数据拉取逻辑放在顶部(数据最快返回)

首屏渲染css及js逻辑优先内联html,返回时能立即执行

次屏逻辑延后执行

DOM构建时间

浏览器开始对基础页文件内容进行解析,构建出一个DOM树的时间。domready事件在DOM加载后、资源加载之前被触发,在本地浏览器的DOMContentLoaded事件的形式被调用。

整页时间

整个页面加载完成时间

loadEvntEnd-navigationStart/onload记录时间戳。

渲染相关

如何减少重绘和回流?

  • 使用 transform 替代 top

  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

  • 不使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

  • CSS选择符从右往左匹配查找,避免节点层级过多

  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。(will-change属性或者video,iframe标签等)

  • 节点属性不要放在一个循环里当循环变量

    //每次都要去取正确的值才行
    for(let i=0;i<100;i++){
        console.log(document.querySeletor('.test').style.offsetTop);
    }

为什么操作DOM的性能很差?

因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。

操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题

插入几万个 DOM,如何实现页面不卡顿?

解决问题的重点应该是如何分批次部分渲染 DOM。

  • 通过 requestAnimationFrame 的方式去循环的插入 DOM;

  • 通过虚拟滚动

    这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。

    即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM。

不考虑缓存和优化网络协议的前提下,可以通过哪些方式来最快的渲染页面?

这个问题的,其实在了解渲染的过程之后,解决方案已经不言而喻了,无非就是减少生成渲染树的时间了。那么回顾下渲染树是怎么生成的呢:DOM+CSSOM。

所以答案如下:

  1. 从文件大小考虑
  2. script 标签使用上来考虑 async和differ
  3. 从需要下载的内容是否需要在首屏使用上来考虑
  4. 最后就是从 CSS、HTML 的代码书写上来考虑了

图片优化

图片大小如何优化

  • 减少像素点
  • 减少每个像素点能够显示的颜色

图片加载如何优化

  1. 用 CSS 去代替。
  2. 用 CDN 加载,计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
    • 尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量。
    • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
    • 照片使用 JPEG

防抖节流

防抖

如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。频繁触发,有足够空闲时间才执行

const debounce = (func, wait = 50) => {
  let timer = 0
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

节流

防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

function throttle(func,wait){
    let last;
    return function(...args){
        let now=+new Date();
        if(!last||now>last+wait){
            last=now;
            func.apply(this,args);
        }
    }
}

预渲染

通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染

<link rel="prerender" href="http://example.com"> 

预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染。

懒执行

懒执行就是将某些逻辑延迟到使用时再计算

该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。

懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。

懒加载

懒加载就是将不关键的资源延后加载

懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。

对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。

懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。

CDN

CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。

因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。