前言
网站的划分一般为二: 前端和后台. 我们可以理解成后台是用来实现网站的功能的, 比如: 实现用户注册, 用户能够为文章发表评论等等. 而前端呢? 其实应该是属于功能的表现. 并且影响用户访问体验的绝大部分来自前端页面.
而我们建设网站的目的是什么呢? 不就是为了让目标人群来访问吗? 所以我们可以理解成前端才是真正和用户接触的. 除了后台需要在性能上做优化外, 其实前端的页面更需要在性能优化上下功夫, 只有这样才能给我们的用户带来更好的用户体验. 就好像, 好多人问, 男人在找女朋友的时候是不是只看外表, 一些智慧的男人给出了这样的回答: 脸蛋和身材决定了我是否想去了解她的思想, 思想决定了我是否会一票否决她的脸蛋和身材. 同理, 网站也是这样, 网站前端的用户体验决定了用户是否想要去使用网站的功能, 而网站的功能决定了用户是否会一票否决前端体验.
不仅仅如此, 如果前端优化得好, 他不仅可以为企业节约成本, 他还能给用户带来更多的用户, 因为增强的用户体验. 说了这么多, 那么我们应该如何对我们前端的页面进行性能优化呢?
一般说来, web 前端指网站业务逻辑之前的部分, 包括浏览器加载, 网站视图模型, 图片服务, CDN 服务等, 主要优化手段有浏览器访问, CDN, 服务端渲染以及 http 相关等等.
但是做性能优化有一点是很重要的, 就是各个方法之间的 balance, 我们不能以为我们使用了某个方法, 网页体验就能更好, 加载速度就能更快, 所有的性能改进方法都要以实际体验为准.
1, 请减少 HTTP 请求以及 dns 优化:
在浏览器 (客户端) 和服务器发生通信时, 就已经消耗了大量的时间, 尤其是在网络情况比较糟糕的时候, 这个问题尤其的突出.
一个正常 HTTP 请求的流程简述: 如在浏览器中输入 "www.xxxxxx.com" 并按下回车, 浏览器再与这个 URL 指向的服务器建立连接, 然后浏览器才能向服务器发送请求信息, 服务器在接受到请求的信息后再返回相应的信息, 浏览器接收到来自服务器的应答信息后, 对这些数据解释执行.
而当我们请求的网页文件中有很多图片, CSS,JS 甚至音乐等信息时, 将会频繁的与服务器建立连接, 与释放连接, 这必定会造成资源的浪费, 且每个 HTTP 请求都会对服务器和浏览器产生性能负担.
网速相同的条件下, 下载一个 100KB 的图片比下载两个 50KB 的图片要耗费的网络资源更多. 所以, 请减少 HTTP 请求.
解决办法:
资源的合并与压缩
合并 CSS 和 JS 文件, css 压缩以及 js 压缩, 在当前的前端生态中, 我们可以结合 webpack 等工具自动帮我们做压缩, 比如 uglyfy-js 等工具, 甚至是利用 tree shaking 等功能去自动帮我们精简代码, 但是请注意, 不能过度合并资源, 否则会造成首页加载过慢的问题.
图片处理
选择合适的图片类型, 如对色彩要求不严格, 我们可以考虑使用 jpg, 不用支持透明我们可以考虑使用 png24, 支持 webp 格式优先考虑使用 webp 格式(android 上对 webp 支持较好, ios 支持较差, 现在淘宝是采用这种模式,)
小图片采用 base64 的格式, 直接嵌入代码中, 可以帮我们减少 http 请求, 但是同样, 这个会造成我们代码的提及变大, 请求的速度会减慢, 这个就是上面所说的需要一个 balance, 现在精灵图的方式已经不是特别流行了, 目前大互联网站只发现 Facebook 在使用
使用图片预加载和图片懒加载, 对于类似瀑布流布局或电商等图片较多的网站, 合理使用图片预加载的方式可以使首页可以更加快速打开, 并且减少带宽消耗
对于 小 icon, 我们可以考虑使用字体图标, 它比图片更轻量级并且更好控制
2, 请正确理解 Repaint 和 Reflow 已经图层等概念
Repaint(重绘)就是在一个元素的外观被改变, 但没有改变布局 (宽高) 的情况下发生, 如改变 visibility,outline, 背景色等等.
Reflow(回流)就是 DOM 的变化影响到了元素的几何属性(宽和高), 浏览器会重新计算元素的几何属性, 会使渲染树中受到影响的部分失效, 浏览器会验证 DOM 树上的所有其它结点的 visibility 属性, 这也是 Reflow 低效的原因. 如: 改变窗囗大小, 改变文字大小, 内容的改变, 浏览器窗口变化, style 属性的改变等等. 如果 Reflow 的过于频繁, CPU 使用率就会噌噌的往上涨, 所以前端也就有必要知道 Repaint 和 Reflow 的知识.
回流一定会重绘, 但是重绘不一定会造成回流, 所以如果可以实现相同效果, 采用重绘的方式可以帮我节省回流的时间
比如上面提到通过设置 style 属性改变结点样式的话, 每设置一次都会导致一次 reflow, 所以最好通过设置 class 的方式; 有动画效果的元素, 它的 position 属性应当设为 fixed 或 absolute, 这样不会影响其它元素的布局; 如果功能需求上不能设置 position 为 fixed 或 absolute, 那么就权衡速度的平滑性. 还有比如动画使用 translate 的方式而不是用定位的方式, 透明度使用 rgba 而不用使用 opacity 等, 有些不经意的操作也会导致大量回流, 如在循环中使用 offsetWidth 等.
图层: 浏览器会为经常变化的部分形成一个图层, 比如 video 等, 我们可以通过 chrome 的 layout 工具找到图层, 形成一个新图层可以让浏览器只回流变化的部分, 比如使用 will-change 可以将一个元素新建一个图层, 对于 gif 等变化较多的我们可以考虑使用新建图层的方式, 但是注意图层过多也会导致图层合成的时候花费大量时间.
3, 请减少对 DOM 的操作
基本原理: 对 DOM 操作的代价是高昂的, 这在网页应用中的通常是一个性能瓶颈.
天生就慢. 在高性能 JavaScript中这么比喻:"把 DOM 看成一个岛屿, 把 JavaScript(ECMAScript)看成另一个岛屿, 两者之间以一座收费桥连接". 所以每次访问 DOM 都会教一个过桥费, 而访问的次数越多, 交的费用也就越多. 所以一般建议尽量减少过桥次数.
解决办法:
1. 修改和访问 DOM 元素会造成页面的 Repaint 和 Reflow, 循环对 DOM 操作更是罪恶的行为. 所以请合理的使用 JavaScript 变量储存内容, 考虑大量 DOM 元素中循环的性能开销, 在循环结束时一次性写入.
2. 减少对 DOM 元素的查询和修改, 查询时可将其赋值给局部变量, 使用 innerhtml 的方式一次插入.
考虑使用前端 mv * 框架
4. 使用 CDN 加速(内容分发网络)
基本原理:
CDN 的全称是 Content Delivery Network, 即内容分发网络.
简单来说它主要的工作是把我们需要被分发的内容分发到世界各地的各个节点上, 让世界各地的人都可以在距离最近的网络节点拿到想要拿到的内容, 减少网络传输距离从而达到加速的目的.
当用户发起内容请求时, 通过 cdn 厂商的智能 DNS 域名解析拿到 cdn 厂商边缘节点服务器的 ip(cdn 厂商会在运营商注册), 然后向边缘节点服务器发起请求, 请求内容数据 (这件事情由浏览器完成), 边缘节点会检测当前节点是否有数据, 如果没有就去 front(父级节点, 父级可能还会有父级节点, 不同的网络环境策略会略有不同) 节点要, 如果还找不到就去源站拿, 并依次序返回. 如果某个边缘节点可以找到, 会先校验内容有效期, 当确定有效期之后返回给用户
但是使用 CDN 的方式请注意浏览器有有一个同域名并发请求上限, 所以如果 cdn 资源过多, 我们可以考虑使用多个 cdn 域名
5. 将 CSS 和 JS 放到外部文件中引用, CSS 放头, JS 放尾
基本原理:
引入外部文件好处是显而易见的, 而且是项目稍稍复杂一点的时候就有必要了这样做了, 易维护, 易扩展, 方便管理和重复利用.
正确的方式:
JavaScript 是浏览器中的霸主, 为什么这么说, 因为在浏览器在执行 JavaScript 代码时, 不能同时做其它事情, 即每次出现都会让页面等待脚本的解析和执行(不论 JavaScript 是内嵌的还是外链的),JavaScript 代码执行完成后, 才继续渲染页面. 这个也就是 JavaScript 的阻塞特性. 因为这个阻塞的特点, 建议把 JavaScript 代码放到标签以前, 这样既能有效的防止 JavaScript 的阻塞, 又能使得页面的 HTML 结构能更快的释放.
HTML 规范清楚指出 CSS 要放包含在页面的区域内, 这里就不多解释了.
但是有点需要注意, 浏览器 js 会阻止 html 的渲染, 但是我们为什么可以加载 js 下面的资源呢? 其实浏览器内部也是有一个资源请求的预检机制的, 可以在 js 执行时并发的请求资源
5, 在前端框架中使用异步加载以及服务端渲染
为什么需要异步加载
现在流行的使用 webpack 的工具构建的前端工程, 可以合并我们的 js 和 css 以及 image 等资源, 合并为一个文件, 那么是不是我们减少了 http 请求的数量那我们网站的性能优化就更好了呢? no, 可能我们网站静态资源很多, 全部揉进一个 js 文件, 那这个文件会是非常巨大的, 首页资源加载的速度会非常慢, 那么有什么办法可以优化呢? 答案是异步加载, 我们不打包所有的组件文件, 将文件拆分开来, 当我们使用到这个组件的时候, 我们才去加载, 通常我们配合 webpack 以及异步加载方法 import('a.js')
服务端渲染
客户端渲染和服务器端渲染的最重要的区别就是究竟是谁来完成 html 文件的完整拼接, 如果是在服务器端完成的, 然后返回给客户端, 就是服务器端渲染, 而如果是前端做了更多的工作完成了 html 的拼接, 则就是客户端渲染, 例如我们在 react 中我们可以使用 renderToString 将组件渲染为 html
优点:
首屏加载快
相比于加载单页应用, 我只需要加载当前页面的内容, 而不需要像 React 或者 vue 一样加载全部的 js 文件(当然, 单页应用文件加载过大的情况可以使用 code spliting 来解决
SEO 优化
对于单页应用, 搜索引擎并不能收录到 ajax 爬取数据之后然后再动态 js 渲染出来的页面
6. 使用缓存和客户端本地存储
对于很少变化的静态资源, 我们可以考虑使用缓存, 并且设置一定的过期时间, 可以有效的提升页面的加载速度和带宽的节约
来源: https://juejin.im/post/5b400726f265da0f46269297