从输入url到页面渲染可以做哪些性能优化?

上一篇我们罗列了从输入url到页面渲染到底经历了什么?那我们今天再来说说这个步骤中,我们能做哪些的性能优化?

DNS Prefetch

上一篇文章只说了浏览器访问一个域名的时候需要经过DNS解析。其实DNS解析是有一个解析过程的,按浏览器缓存系统缓存路由器缓存ISP(运营商)DNS缓存根域名服务器顶级域名服务器主域名服务器的顺序,逐步读取缓存,直到拿到IP地址。

通过主机名加载一个页面通常仅需要解析DNS一次,但是如果页面的资源文件比如:fonts、images、scripts等都是不同的主机名,DNS会对每一个进行解析。这对于性能来说是个问题,特别是对于移动网络。当一个用户用的是移动网络,每一个DNS查找必须从手机发送到信号塔,然后到达一个认证DNS服务器。手机、信号塔、域名服务器之间的距离可能是一个大的时间等待。

DNS Prefetch,即DNS预解析就是根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS解析时间,来提高网站的访问速度。

如果页面之前访问了,我们可以从浏览器的DNS缓存当中直接读取。减少了解析时间,以及请求次数。

打开DNS Prefetch之后,浏览器会在空闲时间提前将这些域名转化为对应的IP地址,这里为了防止DNS Prefetch阻塞页面渲染影响用户体验,Chrome浏览器的引擎并没有使用它的网络堆栈去进行预解析,而是单独开了8个完全异步的Worker线程专门负责DNS Prefetch。所以很多人认为的DNS Prefetch会影响首屏加载其实是错误的,两者并没有任何关系,所以我们可以大胆放心的使用DNS Prefetch。

如何使用DNS Prefetch?

  • 自动解析

    Chromium使用超链接的href属性来查找要预解析的主机名。当遇到a标签,Chromium会自动将href中的域名解析为IP地址,这个解析过程是与用户浏览网页并行处理的。但是为了确保安全性,在HTTPS页面中不会自动解析。

    <!-- https页面开启DNS Prefetch -->
    <meta http-equiv="x-dns-prefetch-control" content="on">
    
    <!--http页面关闭DNS Prefetch-->
    <meta http-equiv="x-dns-prefetch-control" content="off">
    
  • 手动解析

    <!--xx.xx.xx表示静态资源域名-->
    <link rel="dns-prefetch" href="//xx.xx.xx"> 
    

HTTP“请求-响应”

  • 选用高性能的web服务器,利用nginx强大的反向代理能力实现“动静分离”,实现负载均衡、增大连接池等。
  • HTTP协议一定启用长链接。
  • 将协议有HTTP/1 升级到 HTTP/2。因为HTTP/2 消除了应用层的队头阻塞,拥有头部压缩、二进制帧、多路复用、流量控制、服务器推送等许多新特性,大幅度提升了 HTTP 的传输效率。
  • 利用缓存机制

这里重点说一下缓存。缓存又分为CDN缓存浏览器缓存本地缓存

  1. CDN缓存

    CDN即内容分发网络。CDN往往被用来存放像JS、CSS、图片等不需要业务服务器进行计算的资源。

  2. 浏览器缓存

    浏览器缓存机制有四个方面:Memory Cache、Service Worker Cache、Http Cache、Push Cache。

    这里重点讲一下Http Cache。

    Http Cache是要结合服务端一起才能完成。在缓存数据未失效的情况下,可以直接使用缓存数据,不需要再请求服务器。Http缓存策略又细分为强制缓存协商缓存

    • 强制缓存

      • Expires

        响应header中添加Expires字段来标明失效规则。它的值为服务器返回的到期时间,即下一次请求时,请求时间小于服务器返回的到期时间,直接使用缓存数据。

        response.setDataHeader("Expires", "0");//无缓存
        
      • Cache-Control(优先级高于Expires)

        Cache-Control是最重要的规则。常见的取值有private、public、no-cache、max-age、no-store,默认为private。

        1. private:只能针对个人用户,而不能被代理服务器缓存;
        2. public:指示响应可被任何缓存区缓存;
        3. no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。
        4. max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;
        5. no-store:禁止一切缓存
    • 协商缓存

      • ETag/If-None-Match(优先级高于Last-Modified/If-Modified-Since)

        服务器响应请求时,通过Etag头部告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定),浏览器再次请求时,就会带上一个头If-None-Match,这个值就是服务器上一次给的Etag的值,服务器比对一下资源当前的Etag是否跟If-None-Match一致,不一致则说明资源修改过了,浏览器不能再使用缓存,否则浏览器可以继续使用缓存,并返回304状态码。

      • Last-Modified/Last-Modified-Since

        服务器响应请求时,会告诉浏览器一个告诉浏览器资源的最后修改时间:Last-Modified,浏览器之后再请求的时候,会带上一个头:If-Modified-Since,这个值就是服务器上一次给的 Last-Modified 的时间,服务器会比对资源当前最后的修改时间,如果大于If-Modified-Since,则说明资源修改过了,浏览器不能再使用缓存,否则浏览器可以继续使用缓存,并返回304状态码。

    Http缓存的流程图如下图所示:
    在这里插入图片描述

  3. 本地缓存

    本地缓存可以使用Cookie、Local Storage、Session Storage、IndexedDB。

    • Cookie

      HTTP1.0中协议是无状态的,但在WEB应用中,在多个请求之间共享会话是非常必要的,所以出现了Cookie。

      第一次访问网站的时候,浏览器发出请求,服务器响应请求后,会将cookie放入到响应请求中,在浏览器第二次发请求的时候,会把cookie带过去,服务端会辨别用户身份,当然服务器也可以修改cookie内容。

      特性:

      • Cookie一旦创建成功,那么名字无法进行修改;
      • Cookie不支持跨域,这是由Cookie隐私安全性所决定的,这样能够阻止非法获取其它网站的Cookie;
      • 每个单独的域名下面的Cookie数量不能超过20个。
      • 同一个域名下的所有请求,都会携带 Cookie。
      • Cookie 是有体积上限的,它最大只能有 4KB。

      使用方法:

      // 服务端读取cookie
      const cookie = resquest.headers.cookie;
      // 服务端设置cookie
      response.setHeader('Set-Cookie', 'cookie1=abc;');
      
      // 读取cookie 返回字符串
      const allCookies = document.cookie;
      
      // 增加cookie  newCookie是一个键值对形式的字符串。需要注意的是,用这个方法一次只能对一个cookie进行设置或更新。
      document.cookie = 'cookie1=abc;';
      

      可选的cookie属性值(用来具体化对cookie的设定/更新),使用分号分隔:

      • path: 表示 cookie 影响到的路由,如 path=/。如果路径不能匹配时,浏览器则不发送这个Cookie
      • httpOnly: 如果在Cookie中设置了HttpOnly属性值true,那么通过JavaScript脚本将无法读取到cookie信息,保证Cookie不会被泄露,这样能有效的防止XSS攻击;
      • name: Cookie的名称,Cookie一旦创建,名称便不可更改;一个域名下绑定的cookie,name不能相同,相同的name的值会被覆盖掉。
      • value: Cookie的值。如果值为Unicode字符,需要为字符编码;如果值为二进制数据,则需要使用BASE64编码;
      • expires: 是一个绝对的过期时间,如果没有指定或为0表示当前会话有效;
      • maxAge: 是以秒为单位的,是一个相对时间。正常情况下,max-age的优先级高于expires。Max-Age为正数时,cookie会在Max-Age秒之后,被删除,当Max-Age为负数时,表示的是临时储存,不会生出cookie文件,只会存在浏览器内存中,且只会在打开的浏览器窗口或者子窗口有效,一旦浏览器关闭,cookie就会消失,当Max-Age为0时,又会发生什么呢,删除cookie,因为cookie机制本身没有设置删除cookie,失效的cookie会被浏览器自动从内存中删除,所以,它实现的就是让cookie失效。
      • domain: 可以访问该Cookie的域名,注意第一个字符必须为“.”;
      • comment: Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明。
      • secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
    • Local Storage

      LocalStorage 是持久化的本地存储,体积一般是5MB;存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;

      使用方法:

      // 保存数据
      localStorage.setItem("key", "value");
      // 读取数据
      var lastname = localStorage.getItem("key");
      // 删除数据
      localStorage.removeItem("key");
      // 移除所有
      localStorage.clear();
      

      参考:

      MDN window.localStorage菜鸟教程

    • Session Storage

      Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

      使用方法:

      // 保存数据
      sessionStorage.setItem("key", "value");
      // 读取数据
      var lastname = sessionStorage.getItem("key");
      // 删除数据
      sessionStorage.removeItem("key");
      // 移除所有
      sessionStorage.clear();
      

      参考:

      MDN Window.sessionStorage

    • IndexedDB

      IndexDB 是一个运行在浏览器上的非关系型数据库。它不仅可以存储字符串,还可以存储二进制数据。

      使用方法:

      见阮一峰老师的浏览器数据库IndexedDB入门教程HTML5 indexedDB前端本地存储数据库实例教程

解析渲染页面

上一篇讲到了渲染的流程。那么渲染流程中又能做哪些优化呢?

  1. 渲染优化

    • css阻塞

      从上篇渲染流程图上就可以看出来,css解析为CSSOM和html解析为DOM是并行进行的。因为html是先解析的,往往需要等待css解析。这就造成了CSS阻塞了相关的渲染。

      优化办法:

      1. 把css样式表全部通过<style>标签内联到网页中
      2. 将静态资源放到CDN上。
    • JS阻塞

      JS引擎是独立于渲染引擎存在的。Javascript既会阻塞HTML解析,也会阻塞CSS解析。

      优化方法:

      1. 尽量将Javascript文件放在body的底部。
      2. body中间尽量不要写<script>标签。
      3. 使用defer和async来避免不必要的阻塞。
  2. 重绘与回流

    回流(Reflow):当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)

    引发回流的操作:

    1. 页面首次渲染
    2. 浏览器窗口大小改变
    3. 元素的尺寸和位置发生改变
    4. 元素内容发生改变
    5. 添加或者删除DOM元素
    6. 激活css伪类
    7. 设置style属性
    8. 查询某些属性(offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle)或者某些方法(scrollIntoView()、scrollIntoViewIfNeeded()getBoundingClientRect()、scrollTo())

    重绘(Repaint):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

    引发重绘的操作:

    1. color、background 相关属性(如:background-color、background-image 等)
    2. outline 相关属性( outline-color、outline-width )、text-decoration
    3. border-radius、visibility、box-shadow

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

    减少重绘与回流:

    css:

    1. 避免使用table布局。
    2. 尽可能在DOM树的最末端改变class。
    3. 避免设置多层内联样式。
    4. 将动画效果应用到position属性为absolute或fixed的元素上。
    5. 避免使用CSS表达式(例如:calc())。

    javascript:

    1. 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
    2. 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
    3. 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
    4. 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
    5. 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

    参考文献:

    前端性能优化

    回流与重绘

    回流VS重绘

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值