web前端性能优化

3,018 阅读19分钟

引言

前端是庞大的,包括HTMLCSSJavascriptImageFlash等等各种各样的资源。

前端优化是复杂的,针对方方面面的资源都有不同的方式。那么,前端优化的目的是什么 ?

  • 用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
  • 服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。

资源的合并与压缩

1. 资源的合并(减少http请求数量)

如上图所示,文件不合并存在的问题:

  1. 文件与文件之间有插入的上行请求,增加了N-1个网络延迟。
  2. 丢包问题影响更严重,每次网络请求都会存在丢包的情况。
  3. keep-alive经过代理服务器时可能会被断开 ,不一定能完成状态的保持。

但是进行文件合并也是存在的问题:

  1. 首屏渲染问题: 在进行js文件合并后,文件明显变大、请求时间变长。当html网页进行渲染时,若该渲染需要依赖这个js的话,那页面渲染会等到js文件请求之后回来才会继续进行,导致首屏渲染时间延迟。
  2. 缓存失效问题:若现在有a、b、c三个文件合并,只要任意改变其中一个文件都会导致这个合并文件缓存失效,所以文件合并容易导致大面积缓存失效。

对于文件合并有以下三个建议

  1. 公共库合并 ,业务代码单独处理。
  2. 不同页面的js文件单独打包。
  3. 在真实场景做相应处理,怎么合适怎么来 。

如何进行文件合并:

  1. 使用在线网站进行文件合并 。
  2. 在构建阶段,使用nodejs实现文件合并。(webpackgulpfis3等)

2. 资源的压缩(减少请求资源的大小)

  1. HTML压缩 HTML代码压缩就是压缩一些在文本文件中有意义,但是在HTML中不显示的字符,包括空格制表符换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩。

  2. CSS压缩 CSS代码压缩就是对一些无效代码删除CSS语义合并。对于我们来说无效代码可能是注释,也可能是无效字符。

  3. JS压缩与混乱 JS代码压缩就是对一些无效字符的删除剔除注释代码语义的缩减和优化(如变量名缩短等)和代码保护。(代码进行压缩与混乱防止代码逻辑被轻易窥探)

  4. 如何进行HTML、CSS、JS压缩

  5. 使用在线网站进行压缩。

  6. 结合nodejs使用webpackgulpfis3等工具。

图片的优化

1. 各种类型的图片

不同格式图片常用的业务场景

  • jpg有损压缩,压缩率高,不支持透明 —— 大部分不需要透明图片的业务场景
  • png支持透明,浏览器兼容好 (可降阶压缩:png32、png24、png8) —— 大部分需要透明图片的业务场景
  • webp压缩程度更好,在ios webview有兼容性问题 ,在android中支持比较好 —— 安卓开发
  • svg矢量图,代码内嵌,相对较小 —— 图片样式相对简单的业务场景(小的icon、logo等)
  • gif支持透明、体积小、成像相对清晰(静态GIF和动画GIF) —— 与其他4种图片的使用场景基本不冲突

压缩后的大小:png > jpg > webp

一个好用的图片优化平台

2. 进行图片压缩

  1. Css雪碧图:把一些图片整合到一张单独的图片中
  • 优点:减少你的网站的HTTP请求数量。
  • 缺点:整合图片比较大时,一次加载比较慢,替换图片也比较麻烦。
  1. Image inline:将图片的内容内嵌到html当中
  • 优点:减少你的网站的HTTP请求数量。
  • 缺点:适合比较小的图。
  1. 使用SVG矢量图

CSS和JS的装载与执行

1. HTML渲染过程的特点

  1. 顺序执行
  • 进行词法分析,从上到下。
  1. 并发加载
  • 外部资源并发请求,并发度受浏览器域名限制,单个域名并发度是有限制的。
  1. 是否阻塞
  • css在head中通过link方式引入的话会阻塞页面的渲染。
  • css不阻塞外部资源的加载 ,会阻塞js的执行。
  • js的引入会阻塞html文档的分析、页面的渲染。
  • js不阻塞外部资源的加载。
  • js顺序执行,阻塞后续js逻辑的执行。
  1. 依赖关系
  • 页面渲染依赖于css的加载。
  • js的执行顺序是有依赖关系。
  • js逻辑对于dom节点的依赖关系。
  1. 引入方式(js)
  • 脚本直接引入:浏览器会立即加载并执行脚本,js的加载和执行会阻塞页面的渲染。(同步)
  • defer方式引入:不会阻塞页面的渲染,执行时间延迟到dom树构建完成后(DOMContentLoaded事件触发之前完成,保证能拿到dom元素),顺序执行。(异步)
  • async方式引入:不会阻塞页面的渲染,onload事件触发前执行,不保证执行顺序,脚本一旦加载完毕就会立刻执行。(异步)
  • 动态创建script方式:代码动态创建script方式引入,将script标签插入到DOM中。(异步)

DOMContentloaded与onload的区别

  • 当onload事件触发时,页面上所有的DOM、样式表、脚本、图片、flash都已经加载完成。
  • 当DOMContentloaded事件触发时,仅当DOM加载完成,不包括样式表、脚本、图片、flash。

2. 加载和执行的一些优化点

  1. css 样式表置顶
  • 会阻塞页面的渲染,防止页面在没有css样式的情况下渲染出来。
  1. 用link代替import引入css
  • import不会触发浏览器并发机制,页面加载渲染完成之后才会进行import的工作(目前有的高版本的浏览器,link和import的作用没有区别),但是引用层数还是有影响并发。
  1. js 脚本置底
  • 不阻塞页面的渲染和css的加载,使css资源尽量在第一批并发下载中,让页面更快的呈现给用户。
  1. 合理使用js的异步加载

懒加载和预加载

1. 懒加载

  1. 原理
  • 当图片进入可视区域后请求图片资源,需要去监听scroll事件的回调,去判断我们的懒加载图片是否进入可视区域。
  • 注意getBoundingClientRect用于获取某个元素相对于视窗的位置集合,若图片top小于视窗的高度,说明进入可视区域。
  1. 优点
  • 减少无效资源的加载。
  • 并发加载资源过多会阻塞js的加载,影响网站的正常使用。
  1. 实现方式
  • 自己编写懒加载代码lazyload.js
<!doctype html>
<html>
  <head>
    <title>懒加载</title>
  </head>
  <body>
    <div class="image-list">
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.1.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.2.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.3.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.4.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.5.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.6.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.7.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.8.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.9.jpg" />
      <img src="" class="image-item" lazyload="true" data-original="http://xxx.xxx.10.jpg" />
    </div>
    <script>
      var viewHeight = document.documentElement.clientHeight; //获取可视区域的高度
      function lazyload () {
        var eles = document.querySelectorAll('img[data-original][lazyload]'); //获取需要懒加载的元素
        Array.prototype.forEach.call(eles, function (item, index) {
          var rect;
          if (item.dataset.original === '') return;

          rect = item.getBoundingClientRect(); //获取元素的大小及其相对于视口的位置集合,集合中有top, right, bottom, left等属性。
          if (rect.bottom >= 0 && rect.top < viewHeight) { //判断元素是否进入可视区域
            !function () { //立即执行匿名函数,加载图片
              var img = new Image();
              img.src = item.dataset.original;
              img.onload = function () {
                item.src = img.src;
              }
              item.removeAttribute('data-original');
              item.removeAttribute('lazyload');
            }()
          }
        })
      }

      lazyload(); //初始化懒加载方法
      document.addEventListener('scroll', lazyload); //添加页面滚动监听器
    </script>  
  </body>
</html>
  • 使用网上分享的lazyload库。

2. 预加载

  1. 原理
  • 图片等静态资源在使用前提前请求。
  1. 优点
  • 资源使用时能从缓存中加载,提升用户体验。
  1. 实现方式
  • <img src="http://..." style="display: none" />
  • 使用Image对象,var image = new Image();image.src = "http://...";
  • 使用XMLHttpRequest对象,更好去控制预加载的过程,存在跨域问题。
  • 使用PreloadJS,提供了一个一致的方式预先加载在HTML应用的内容,以及预加载可以使用HTML标签作为XHR完成。

回流与重绘

1. css性能让javascript变慢

  • 频繁触发重绘与回流,会导致UI频繁渲染,最终导致js变慢。

2. 回流

  • 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流(reflow)。当页面布局和几何属性改变时就需要回流。

3. 重绘

  • 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就称为重绘(repaint)。

4. 回流与重绘关系

  • 回流必将引起重绘,而重绘不一定会引起回流。

5. 触发页面重布局(回流)的属性

6. 只触发重绘的属性

7. 新建DOM的过程

1、获取DOM后分隔为多个图层
2、对每个图层的节点计算样式结果(recalculate style)
3、为每个节点生成图形和位置(layout、reflow和重布局)
4、将每个节点绘制填充到图层位图汇总(paint,repaint)
5、图层作为纹理加载到GPU
6、合并多个图层到页面上,生成最终图像(composite layers) 

8. Chrome创建图层的条件

1、3D或透视变换(perspective、transform)CSS属性
2、使用加速视频解码的<video>节点
3、拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点 
4、混合插件(如Flash) 
5、对自己的opacity做CSS动画或使用一个动画webkit变换的元素 
6、拥有加速CSS过滤器的元素 
7、元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里) 
8、元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

9. 实战优化点

1、用translate(重绘)替代top(回流)改变
2、用opacity替代visibility(重绘) 
3、不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的className 
4、把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次回流),然后你修改100次,然后再把它显示出来
5、不要把获取DOM元素的真实位置代码放在一个循环里使用,否则会对相关缓冲区进行刷新,最好存到循环外的变量中再去使用
6、不要使用table布局,可能很小的一个小改动会造成整个 table 的重新布局(回流)
7、动画实现的速度的选择,动画速度快(重绘和回流)的话可能导致页面性能下降
8、对于动画新建图层(例:添加transform CSS属性)
9、添加 CSS3 样式启用 GPU 硬件加速(例:transform: translateZ(0)或transform: translate3d(0, 0, 0))
10、减少对DOM的操作,对DOM操作的代价是高昂的
11、避免使用出发重绘、回流的CSS属性
12、将重绘、回流的影响范围限制在单独的图层之内,但是图层的合成过程比较消耗运算量,图层不能过多

浏览器储存

1. cookie

  1. 因为HTTP请求无状态,所以需要cookie去维持客户端状态。
  2. 可以设置过期时间expire
  3. cookie的两种生成方式及作用
  • http response header中的set-cookie(服务端生成),用于浏览器端和服务器端的交互。
  • js中可以通过document.cookie可以读写cookie(客户端生成),客户端自身数据的存储。
  1. 仅仅作为浏览器存储。(大小4KB左右,能力被localstorage替代)
  2. 所有相关域名请求都会带上cookie,有的请求不需要cookie,造成cdn上静态文件的流量损耗(将cdn域名和主域名独立开)。
  3. httponly,不允许js进行读写,防止攻击。

2. LocalStorage和SessionStorage

  1. LocalStorage
  • HTML5设计出来专门用于浏览器存储的。(没有时间限制)
  • 大小为5M左右。
  • 仅在客户端使用,不和服务端进行通信。
  • 接口封装较好,读写、删除数据方便。
  • 浏览器本地缓存方案。
  1. SessionStorage
  • 会话级别的浏览器存储 (浏览器一个标签页就是一个会话,当签页关闭后数据清空)。
  • 大小为5M左右。
  • 仅在客户端使用,不和服务端进行通信。
  • 接口封装较好,读写、删除数据方便。
  • 适合用于对表单信息的维护。

3. IndexedDB

  1. IndexedDB 是一种低级API,用于客户端存储大量结构化数据。该API使用索引来实现对该数据的高性能搜索。虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。
  2. 为应用创建离线版本。

4. PWA (Progressive Web Apps)

  1. 简介
  • PWA (Progressive Web Apps) 是一种 Web App 新模型,并不是具体指某一种前沿的技术或者某一个单一的知识点,我们从英文缩写来看就能看出来,这是一个渐进式的 Web App,是通过一系列新的 Web 特性,配合优秀的 UI 交互设计,逐步的增强 Web App 的用户体验。
  1. 特点
  • 可靠:在没有网络的环境中也能提供基本的页面访问,而不会出现“未连接到互联网”的页面。
  • 快速:针对网页渲染及网络数据访问有较好优化。
  • 融入:应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。
  1. 缺点
  • 门槛不低(要求 HTTPS,Service Worker 的 API 比较 low-level)
  • 浏览器支持不够完美(Safari 短期内不会支持,在 5 年计划里提了一嘴)
  • 用户习惯 (让用户习惯于网页可以离线工作并不是短期可以达到的)
  1. 性能检测工具 Lighthouse,可以检测网站是否符合PWA、网站的可靠性、速度等性能优化指标[下载地址]。(https://lavas.baidu.com/doc-lavas/vue/more/downloads/lighthouse_2.1.0_0.zip)

5. Service Worker

  1. 简介
  • Service Worker 是一个脚本,浏览器独立于当前网页,将其在后台运行,为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性将包括推送消息,背景后台同步,geofencing(地理围栏定位),但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。Service Worker只能用于https站点中,非https站点不具备Service Worker能力。
  1. 生命周期

  2. 运用

  • 使用拦截和处理网络请求的能力,去实现一个离线应用。
  • 使用Service Worker在后台运行同时能和页面通信的能力,去实现大规模后台数据的处理。
  1. 检测
  • 查看当前浏览器上运行的Service Worker (chrome://inspect/#service-workers)。
  • 查看已注册的Service Worker (chrome://serviceworker-internals)。

缓存

1. 原理

浏览器缓存就是把一个已经请求过的Web资源(如html页面图片js数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。

2. 缓存的好处

  1. 减少网络带宽消耗
  • 无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
  1. 降低服务器压力
  • 给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫机器人也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。
  1. 减少网络延迟,加快页面打开速度
  • 带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那Web缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。

3. 浏览器请求流程

4. 缓存策略

  1. Expires策略
  • Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
  • Expires是HTTP1.0的东西,现在默认浏览器均默认使用HTTP1.1,所以它的作用基本忽略。
  • Expires的一个缺点就是返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP1.1版开始,使用Cache-Control: max-age=秒替代。
  1. Cache-control策略:Cache-control对应值可以是publicprivateno-cacheno-storeno-transformmust-revalidateproxy-revalidatemax-ages-maxage
  • 请求Request:

    1、no-cache:不要读取缓存中的文件,要求向WEB服务器重新请求

    2、no-store:请求和响应都禁止被缓存

    3、max-age:表示当访问此网页后的max-age秒内再次访问不会去服务器请求,其功能与Expires类似,只是 Expires是根据某个特定日期值做比较。一但缓存者自身的时间不准确.则结果可能就是错误的,而max-age, 显然无此问题.。Max-age的优先级也是高于Expires的

    4、max-stale:允许读取过期时间必须小于max-stale 值的缓存对象

    5、min-fresh:接受其max-age生命期大于其当前时间 跟 min-fresh 值之和的缓存对象

    6、only-if-cached:告知缓存者,我希望内容来自缓存,我并不关心被缓存响应,是否是新鲜的

    7、no-transform:告知代理,不要更改媒体类型,比如jpg,被你改成png

  • 响应Response:

    1、public: 数据内容皆被储存起来,就连有密码保护的网页也储存,安全性很低

    2、private:数据内容只能被储存到私有的cache,仅对某个用户有效,不能共享

    3、no-cache:可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端

    4、no-store:请求和响应都禁止被缓存

    5、max-age:本响应包含的对象的过期时间

    6、must-revalidate:如果缓存过期了,会再次和原来的服务器确定是否为最新数据,而不是和中间的proxy

    7、s-maxage:与max-age的唯一区别是,s-maxage仅仅应用于共享缓存.而不应用于用户代理的本地缓存等针对单用户的缓存。另外,s-maxage的优先级要高于max-age

    8、max-stale:允许读取过期时间必须小于max-stale 值的缓存对象

    9、proxy-revalidate:与must-revalidate类似,区别在于proxy-revalidate要排除掉用户代理的缓存的。即其规则并不应用于用户代理的本地缓存上

    10、no-transform:告知代理,不要更改媒体类型,比如jpg,被你改成png

  1. Last-Modified/If-Modified-Since
  • Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。
  • If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头If-Modified-Since表示请求时间。web服务器收到请求后发现有头If-Modified-Since则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304(无需包体,节省浏览),告知浏览器继续使用所保存的cache。
  • 注意:Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间。如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存。有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag一起使用时,服务器会优先验证ETag。
  1. Etag/If-None-Match
  • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
  • If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
  1. 缓存策略用户行为与缓存