网页页面性能优化总结

4,584 阅读8分钟
  • js性能优化是前端面试一个常问知识点。所以我结合我个人一些经验,加上看了其他一些文章点分析。作了一些总结。

一,主要从用户输入网址到页面渲染完成。及与用户交互到方面来分析。

过程:

  • 用户输入网址请求资源 => 将请求到的资源渲染成页面 => 渲染完成的页面交互 => 其他方面的优化
一,用户输入网址请求资源
  1. 当用户输入一个域名网址时,浏览器会将这个域名发送到DNS服务器
    (这一过程中,我们可以通过改变我们host的配置,将域名配置为我们开发环境的服务器ip。实现远程开发);
  2. 通过DNS服务器进行域名解析,拿到对应的服务器ip地址;
  3. 浏览器拿到服务器的ip后,找到对应的服务器;
  • =>这个过程可以做个http缓存优化(见下文——http缓存)
  1. 获取服务器js,html等资源;
二,将请求到的资源渲染成页面
  • =>http页面加载及渲染过程的优化(见下文——页面加载及渲染过程)
三,渲染完成的页面交互
  • =>页面交互过程的优化(见下文——页面交互过程的优化)
四,其他方面的优化
  • =>其他方面的优化的优化(见下文——其他方面的优化)

二,http缓存及优化

  1. 强制缓存
不会向服务器发送请求,直接从缓存中读取资源,
在chrome控制台的network选项中可以看到该请求返回200的状态码,
并且size显示from disk cache或from memory cache;
设置强制缓存的方式:
在响应头设置:
Catch-Control优先级 > Expires优先级

Catch-Control:
比如Cache-Control:max-age=300(秒)
代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存

Expires:
比如Expires:Thu,21 Jan 2020 23:39:02 GMT
response header里的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。
它的值为一个绝对时间的GMT格式的时间字符串;
存在一个缺点:但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差,因此现在常用这个;
  1. 协商缓存
协商缓存:向服务器发送请求,服务器会根据这个请求的request
header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response
header通知浏览器从缓存中读取资源;另外协商缓存需要与cache-control共同使用。;
设置协商缓存的方式:
在响应头设置:
Etag优先级 > Last-Modified优先级

Etag:
服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定);
If-None-Match: 再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。
服务器接收到次报文后发现If-None-Match则与被请求资源的唯一标识进行对比:
a. 不同,说明资源被改动过,则响应整个资源内容,返回状态码200。
b. 相同,说明资源无心修改,则响应header,浏览器直接从缓存中获取数据信息。返回状态码304.
c. 但是实际应用中由于Etag的计算是使用算法来得出的,而算法会占用服务端计算的资源,
所有服务端的资源都是宝贵的,所以就很少使用Etag了。

Last-Modified: Fri, 22 Jul 2020 01:47:00 GMT
服务器在响应请求时,会告诉浏览器资源的最后修改时间;
if-Modified-Since:
浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。
服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,
如果一致则返回304和响应报文头,浏览器只需要从缓存中获取信息即可。
从字面上看,就是说:从某个时间节点算起,是否文件被修改了:
a. 如果真的被修改:那么开始传输响应一个整体,服务器返回:200 OK
b. 如果没有被修改:那么只需传输响应header,服务器返回:304 Not Modified

if-Unmodified-Since:
从字面上看, 就是说: 从某个时间点算起, 是否文件没有被修改
如果没有被修改:则开始继续传送文件: 服务器返回: 200 OK
如果文件被修改:则不传输,服务器返回: 412 Precondition failed (预处理错误);

这两个的区别是一个是修改了才下载一个是没修改才下载。
Last-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,
会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
为了解决这个问题,HTTP1.1推出了Etag。

  1. 强制缓存优先级 > 协商缓存优先级;

  2. 刷新

用户行为对浏览器缓存的影响
1.地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制;
2.F5刷新,浏览器会设置max-age=0,跳过强缓存判断,会进行协商缓存判断;
3.ctrl+F5刷新,跳过强缓存和协商缓存,直接从服务器拉取资源。
  1. 缓存的优点
a. 减少了冗余的数据传递,节省宽带流量
b. 减少了服务器的负担,大大提高了网站性能
c. 加快了客户端加载网页的速度 这也正是HTTP缓存属于客户端缓存的原因。

三,页面加载及渲染过程(HTML,Js,css等资源的加载与执行)

  1. 当浏览器在解析dom树的时候,如果遇到script标签。会阻塞其他任务的执行。直到script标签加载,解析完成;
  2. 这个时候页面会一片空白。用户体验很不好,可以使用以下做法;
  • => A, 将script标签放在body的最底部,保证js文件最后加载并执行;同时不影响页面加载;
  • => B, 或者使用defer,如下写法。这种方式可以使浏览器解析到script标签时,也会加载js。但他会等到dom树解析完成后再去执行js(DomContentLoader之前执行),因此不会阻塞浏览器执行;
<script src="test.js" type="text/javascript" defer></script>
  • => C, 动态的插入script标签来加载脚本,比如通过以下代码(参考);
 function loadScript(url, callback) {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    // 处理IE
    if (script.readyState) {
      script.onreadystatechange = function () {
        if (script.readyState === 'loaded' || script.readyState === 'complete') {
          script.onreadystatechange = null;
          callback();
        }
      }
    } else {
      // 处理其他浏览器的情况
      script.onload = function () {
        callback();
      }
    }
    script.src = url;
    document.body.append(script);
  }

  // 动态加载js
  loadScript('file.js', function () {
    console.log('加载完成');
  });
  1. 解析css时。不会操作html元素。因此不会阻塞dom树的渲染;同时dom树和css都解析完成后,合并生成渲染树;
  2. 浏览器按照从上到下,从左到右的顺序。绘制生成渲染树。展示页面给用户;

四,渲染完成的页面交互

  1. 滚动条调用接口时,可以用节流throttle等优化方式,减少http请求;
  2. 输入搜索时,可以用防抖debounce等优化方式,减少http请求;
  3. 减少dom元素对操作次数(参考);
    // 优化前
  const el = document.getElementById('test');
  el.style.borderLeft = '1px';
  el.style.borderRight = '2px';
  el.style.padding = '5px';
 // 优化后,一次性修改样式,这样可以将三次重排减少到一次重排
  const el = document.getElementById('test');
  el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'
  1. 使用事件委托;
  2. 应该尽可能的减少对象成员的查找次数和嵌套深度;
  3. 降低循环遍历次数(特别对于多维数组的匹配时);
let arr1 = [{id: 12, name: 'aa'}, {id: 33, name: 'aa'}];// => {12: {id: 12, name: 'aa', 33: {id: 12, name: 'aa'}};
let arr2 = [{id: 12, age: '18'}, {id: 33,age: '22' }];

五,其他方面的优化

  1. 对js,css等资源进行源码压缩,减小文件大小;
  2. 合理控制图片大小,对长期固定图片使用base64编码,使用雪碧图等;
  3. 设置样式时,尽量使用className,少用style;
  4. 少用全局变量,避免全局搜索;
  5. 方便时多使用原生方法;
  6. 使用第三方资源时。尽量做到按需加载;