• 前端页面渲染机制、提高页面渲染效率


    本篇文章主要从两个方面讲解页面渲染机制,即网络方面渲染引擎方面

    网络

    当用户访问页面时,浏览器需要获取用户请求内容,这个过程主要涉及浏览器网络模块。

    • 用户在地址栏输入域名,比如,baidu.com
    • DNS(又称域名解析系统,默认端口号53)协议,通过域名查找IP地址

        浏览器DNS解析大多时候比较快,且会缓存常用域名的解析值,但是如果网站涉及多域名,在对每一个域名访问时都需要先解析出IP地址,而我们希望在跳转或者请求其他域名资源时尽量快,则可以开启域名预解析浏览器会在空闲时提前解析声明需要预解析的域名。如下

          

        即,在网页头部(head之间)增加rel属性为”dns-prefetch”的link标签,并在href中指定想要预解析的域名。

    • 向该IP地址发起请求。请求过程如下

      客户端:

        HTTP协议生成针对目标Web服务器的HTTP请求报文;

        为了方便通信,TCP协议将HTTP报文分割成报文段,把每个报文段可靠地传给对方;

        ARP协议根据IP地址解析出对应的MAC地址,IP协议根据MAC地址传送报文。这期间可能会经过多个路由器,IP协议自动中转,直到找到目的MAC;

      服务器端:

        TCP协议从客户端接收到报文段,按序号以原来的顺序重组请求报文;

        HTTP协议对Web服务器请求的内容进行处理。此时,服务器知道了客户端想要浏览 baidu.com 这个页面了;

    • 浏览器获得并解析服务器的返回内容(HTTP Response)。响应过程如下

      服务器端:

      客户端:

    • 浏览器加载HTML文件以及文件内包含的外部引用文件以及图片,多媒体等资源。

    渲染搜索引擎(关键渲染路径)

    渲染引擎所做的事情是将请求内容展现给我们,默认支持HTML,XML和图片类型,对于其他诸如PDF等类型的内容则需要安装响应插件,但浏览器的展示工作流程基本是一样的。

    通过网络模块加载到HTML文件后,渲染引擎渲染流程如下

    • 从 Head 标签开始逐行解析HTML代码,遇到 link 标签又会向服务器请求加载CSS文件,这个过程是异步加载。如果有多个CSS文件,会同时加载。
    • 如果遇到 script 标签或者 js 文件,会立即执行,并且这个过程是异步的。

        不同于CSS文件,js 是同步加载。即执行js文件时,浏览器不会做其他事情,只有js代码执行结束后,才会继续开始渲染页面。为了防止出现“空白页”现象,应该把 js 放到页面底部,也就是</body>标签前。

    • 然后到 body 标签开始渲染页面,按照从上到下的顺序依次渲染dom节点。如果遇到 img 标签,会异步向服务器发送请求加载图片文件,浏览器会继续渲染页面,因为图片加载是异步的~
    • 如果遇到了dom节点的变化,元素尺寸变化,浏览器不得不回头重新渲染这部分代码。

    以上就是本篇文章介绍的浏览器页面渲染机制了,下面简单介绍下如何提升页面的渲染效率~

    影响页面渲染速率

    • 回流(reflow)

        当浏览器发现页面某个部分发生了变化影响了布局,需要倒回去重新渲染,该过程称为回流。

    • 重绘(repaint)

        如果只是改变了某个元素的边框颜色、字体颜色、背景色等不影响它周围或内部布局的属性,将只会影响浏览器的重绘。

    提升页面渲染速率

    • CSS注意事项

      用CSS动画替代js模拟动画的好处是:不占用js主线程;可以利用硬件加速;浏览器可对动画做优化。但CSS动画有时会出现卡顿现象

      使用CSS3动画造成页面的不流畅和卡顿问题,其潜在原因往往还是页面的回流和重绘,减少页面动画元素对其他元素的影响是提高性能的根本方向。

      1、设置动画元素 position 样式为absolute或 fixed,可避免动画的进行对页面其它元素造成影响,导致其重绘和重排的发生;

      2、避免使用margin,top,left,width,height等属性执行动画,用 transform 进行替代;

      下面有一个CSS动画,动画开始后,你会隐约感觉到动画不是那么流畅,即使使用电脑上的浏览器也会有些卡顿,更不要提在移动端达到 60 fps的流畅效果了。。。

      
      .div {
        animation: run-around 4s infinite;
      }
      @keyframes run-around { 
        0% {
          top: 0;
          left: 0;
        }
        25% {
          top: 0;
          left:200px;
        }
        50% {
          top: 200px;
          left: 200px;
        }
        75% {  
          top: 200px;
          left: 0px;
        }
      }

      为了解决这个问题,我们可以使用CSS transform中的 translate() 来代替 top 和 left。

      .div {
        animation: run-around 4s infinite;
      }
      @keyframes run-around {  
        0% {
          transform: translate(0,0);
        }
        25% {
          transform: translate(200px,0);
        }
        50% {  
          transform: translate(200px,200px);
        }
        75% {
          transform: translate(200px,0);
        }
      }

       现在动画看起来会好很多。因为transform属性不会出发浏览器的 repaint,而 top 和 left 会一直触发 repaint。为什么 transform 没有触发 repaint 呢?因为,transform 动画由GPU控制,支持硬件加速,并不需要软件方面的渲染。

      GPU详细介绍,可以参考 https://www.jianshu.com/p/d1e16a2e88c1 这篇文章。

    • JS注意事项

      解决js同步加载问题(有下面三种方式)  

       1、将js文件放在页面底部,即</body>标签之前。因为html文件默认是按照顺序从上到下依次加载的,这样就可以先渲染dom节点,再加载js

       2、使用 H5 的async属性,用法和特点如下

      <script src = "test.js"  anysc></script>
      //加载脚本时不阻塞页面渲染
      //使用这个属性的脚本中不能调用document.write方法
      //可以只写属性名,不写属性值。写法如上
      //H5新增属性
      //脚本在下载后立即执行,同时会在window的load事件之前执行,所以有可能出现脚本执行顺序被打乱的情况

       3、使用HTML的defer属性,用法和特点如下(前三点和anysc相同)

      <script src = "test.js" defer></script>
      //加载脚本时不阻塞页面渲染
      //使用这个属性的脚本中不能调用document.write方法
      //可以只写属性名,不写属性值。写法如上
      //H4属性
      //脚本在页面解析完之后,按照原本的顺序执行,同时会在document的DOMContentLoaded之前执行

     避免频繁操作DOM元素

       1、当需要很多的插入操作和改动,使用下面的代码会很有问题

      var ul = document.getElementById("ul");
      for (var i = 0;i < 20;i++){
        var li = document.creatElement("li");
        ul.appendChild(li);
      }

       由于每一次对文档的插入都会引起重新渲染(计算元素的尺寸、显示背景、内容等),所以进行多次插入操作使得浏览器发生了多次渲染,效率比较低。这是我们提倡减少页面的渲染来提高DOM操作的效率的原因。

      createDocumentFragment()方法,是用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点,它可以包含各种类型的节点,在创建之初是空的。它有一个很实用的特点,当请求把一个createDocumentFragment 节点插入文档树时,插入的不是createDocumentFragment 自身,而是它所有的子孙节点。这个特性使得createDocumentFragment 成了占位符,暂时存放那些一次插入文档的节点。

      另外,当需要添加多个dom元素时,如果先将这些元素添加到createDocumentFragment 中,再统一将createDocumentFragment 添加到页面。因为文档片段存在于内存中,并不在DOM中,所以将子元素插入文档片段中不会引起回流(对元素位置和几何上的计算),因此,使用DocumentFragment可以起到性能优化的作用。可以将上面的代码改成下面的

      var ul = documen.getElementById("ul");
      var fragment = document.createDocumentFragment();
      for (var i = 0;i < 20;i++){
        var li = document.createElement("li");
        li.innerHtml =" index: " + i;
        fragment.appendChild(li);
      }
      ul.appendChild(fragment);

      关于 createDocumentFragment() 方法更多介绍,请读者自行查阅~

      2、设置DOM元素的display属性为none再操作该元素

      var myElement = document.getElementById('myElement');
      myElement.style.display = 'none';
      
      …… //一些基于myElement的大量DOM操作

      myElement.style.display = 'block';

     3、复制DOM元素到内存中再对其进行操作

      var old = document.getElementById('myElement');
      var clone = old.cloneNode(true);

      …… //一些基于clone的大量操作


      old.parentNode.replaceChild(clone,old);

     4、用局部变量缓存样式信息从而避免频繁获取DOM数据

      比如访问一个元素的offsetWidth属性时,浏览器需要重新计算(重新布局),然后才能返回最新的值,如果这个动作发生在一个很大的循环中,那么浏览器就不得不进行多次重新布局,这可能会产生严重的性能问题。正确的做法是,先将这个值读出来,然后缓存在一个变量上(触发一次重新布局)。以便后续使用

      
      //一般用法
      for (var i = 0;i < paragraphs.length;i++) {
        paragraphs.style.width = box.offsetWidth + 'px';
      }

      //优化性能的用法
      var width = box.offsetWidth;
      for (i = 0;i < paragraphs.length;i++){
        paragraphs[i].style.width = width + 'px';
      }

     5、合并多次DOM操作

      
      //一般用法
      var left = 10;top = 10;
      el.style.top = top;
      el.style.left = left;
      
      //优化性能写法
      el.style.cssText += ";left: " + left + "px; top: " + top + "px;";

    • 其他

      除了CSS 和 JS 的改进能够提升页面渲染速率,还有其他方面的改进同样能够提升页面渲染速率。

      1、资源压缩与合并。

        HTML代码压缩:压缩在文本中有意义,而在HTML中不需要的字符。比如,空格、制表符、换行符,还有一些其他意义的字符,如HTML注释也可以被压缩。

        CSS代码压缩:删除无效的代码和css语义合并。

        JS的压缩和紊乱:使用在线网站压缩、使用html-minifier工具、使用uglifyjs2进行压缩。

        文件合并:将多个js/css小文件合并为一个文件,减少网络请求次数。

        注:css压缩与js的压缩和紊乱比html压缩收益要大的多,同时css代码和js代码比html代码多的多。所以,css与js代码压缩非常有必要! 

       2、浏览器缓存

        缓存作用:对于web应用来说,缓存是提升页面性能同时减少服务器压力的利器。

        强缓存:不会向服务器发送请求,直接从缓存中读取资源,在Chrome控制台的network选项中可以看到该请求的状态码是 200,但是 size 的标识为 from dist cache 或者 from memory cache

        response header:response header里的过期时间,浏览器再次加载该资源时,如果在有效时间内,则使用强缓存。

        Last-Modified 和 If-Modified-Since:二者都是记录页面最后修改时间的 HTTP 头信息,Last-Modified 是由服务器往客户端发送的HTTP, If-Modified-Since 是客户端往服务器端发送的头。

        再次请求本地缓存的 cache 页面时,客户端会通过 If-Modified-Since 头先将服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的。如果不是最新的,则返回新的内容,如果是最新的,则返回 304 告诉客户端其本地 cache 的页面时最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器端的负担。而在一些ajax应用中,要求回获取的数据永云是最新的,而不是读取缓存中的数据,做这样的设置是很有必要的。

       3、CDN预解析

      CDN服务提供商会有全国各个省份部署节点, 将网站静态资源部署到CDN后, 用户在访问页面时, CDN静态资源会从就近的CDN节点上加载资源. 当请求至达CDN节点后, 节点会判断资源是缓存是否有效, 若有效, 直接返回给用户, 若无效, 会从CDN服务器加载最新的资源返回给用户同时将资源保存一份到该CDN节点上, 以便后续的访问用户使用. 因此, 只在该地区有一个用户先加载了资源, 在CDN中建立了缓存, 该地区的其他用户都能受益。

      4、DNS预解析

      DNS 作为互联网的基础协议,其解析的速度似乎容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析耗费20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式。DNS Prefetching是具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能减少用户的等待时间,提升用户体验。
     

        

  • 相关阅读:
    leetcode-654-最大二叉树
    leetcode-46-全排列
    图片懒加载?
    HTTP常见的状态码?
    线程与进程的区别?
    网页从输入网址到渲染完成经历了哪些过程?
    网页前端性能优化的方式有哪些?
    常见的浏览器内核有哪些?
    汇编语言--cpu的工作原理(寄存器)--手稿
    对于 vue3.0 特性你有什么了解的吗?
  • 原文地址:https://www.cnblogs.com/baby-zuji/p/11172321.html
Copyright © 2020-2023  润新知