前言
最近跟同事一直忙于关于前端项目的性能分析以及性能优化,前端性能直接影响了用户的体验,针对于前端性能问题,一直是一个大家热议的话题,也并没有一个比较完整通用的解决方案,以下是我个人的一些认识与实践。
性能优化概述
从输入 URL 到页面加载完成,完整的链路
1.DNS 解析
2.TCP 连接
3.HTTP 请求抛出
4.服务端处理请求,HTTP 响应返回
5.浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
整个性能消化
http层面优化
- DNS 解析:
DNS 实现域名到IP的映射。通过域名访问站点,每次请求都要做DNS解析。目前每次DNS解析,通常在200ms以下。一般采用DNS Prefetch 一种DNS 预解析技术,当你浏览网页时,浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。
<link rel="dns-prefetch" href="www.baidu.com" />
只有部分浏览器支持
TCP 连接:
采用http2.0,可以复用tcp通道,采用二进制格式而非文本格式,使用报头压缩,HTTP/2降低了开销,支持cache push浏览器并发
基于端口跟线程切换开销,浏览器不可能无限的并发请求。chrome的并发为6,超过限制数目的请求就会被阻塞;
对于某些静态资源,图片等等,我们可以对其URL分散处理 ,不同的资源域名(部署在cdn上)。http请求次数
减少http的请求次数,将多个请求合并成同一个,减少http的开销webpack
充分利用webpack提供给我们的能力,利用DllPlugin与commonPlugins等插件对我们代码进行
优化,文件的分割与合并,公共代码的提取,长缓存等策略,webpack是个很好的东西,值得大家仔细研究http压缩
采用Gzip压缩:HTTP 压缩就是以缩小体积为目的,对 HTTP 内容进行重新编码的过程,原理是找出一些重复出现的字符串、临时替换它们,从而使整个文件变小,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大
图片优化
缓存
浏览器缓存
-
强缓存:
浏览器在请求某一资源时,会先获取该资源缓存的header信息,判断是否命中强缓存(cache-control和expires信息),若命中直接从缓存中获取资源信息,包括缓存header信息;本次请求根本就不会与服务器进行通信- expires:这是http1.0时的规范;它的值为一个绝对时间的GMT格式的时间字符串,如Mon, 10 Jun 2015 21:31:12 GMT,如果发送请求的时间在expires之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源
- cache-control:max-age=number,这是http1.1时出现的header信息,主要是利用该字段的max-age值来进行判断,它是一个相对值;资源第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行;cache-control除了该字段外,还有下面几个比较常用的设置值:no-cache ,no-store,public,private
-
协商缓存(对比缓存)
- Last-Modified/If-Modified-Since:第一次请求,服务端在Response Headers :Last-Modified:Fri, 27 Oct 2017 06:35:57 GMT,也就是服务端最后修改该资源的时间。
浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值,服务器进行比较,如果相同则返回304,否则浏览器直接从服务器加载资源时,Last-Modified的Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值 - Etag/If-None-Match: 服务器会为每个资源生成一个唯一的标识字符串,只要文件内容不同,它们对应的 Etag 就是不同的;If-Modified-Since能检查到的精度是s级的,某些服务器不能精确的得到文件的最后修改时间,我们编辑了文件,但文件的内容没有改变。因为服务器是根据文件的最后修改时间来判断的,导致重新请求所以才出现了Etag,Etag对服务器也有性能损耗
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
- Last-Modified/If-Modified-Since:第一次请求,服务端在Response Headers :Last-Modified:Fri, 27 Oct 2017 06:35:57 GMT,也就是服务端最后修改该资源的时间。
-
请求过程总结:
memory cache 与 disk cache
from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。CDN缓存
CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。通常情况下,浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。从浏览器角度来看,整个CDN就是一个源服务器,从这个层面来说,浏览器和服务器之间的缓存机制,在这种架构下同样适用应用缓存
Cookie:同一个域名下的所有请求,都会携带 Cookie,大小限制4kb
Session Storage:用来存储生命周期和它同步的会话级别的信息,关闭浏览器就不存在
Local Storage:持久化缓存 5-10Mb
Service Worker缓存:
pwa,会拦截http请求,对资源进行离线缓存、消息推送,无法直接访问dom,
利用workbox插件非常容易接入pwa技术
浏览器渲染
浏览器渲染机制
- DOM树:
解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。 - CSSOM树:
解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。
-渲染树:
CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。 - 布局渲染树:
从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。 - 绘制渲染树:
遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。
阻塞
- 普通模式,JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执⾏完毕才能去做其它事情。一般将此类js放在在<body>标签的底部,减少对整个页面下载的影响
<script src="index.js"></script>
- async 模式:JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会⽴即执⾏。
<script async src="index.js"></script>
- defer 模式:JS 的加载是异步的,执⾏是被推迟的。等整个⽂档解析完成DOMContentLoaded 事件即将被触发时,被标记了defer 的 JS ⽂件才会开始依次执⾏
<script defer src="index.js"></script>
⼀般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选⽤ async;当脚本依赖于 DOM
元素和其它脚本的执⾏结果时,我们会选⽤ defer。
- 动态加载脚本:此文件当元素添加到页面之后立刻开始下载。无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响
var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
服务器ssr渲染
定义:
服务端渲染的模式下,当⽤户第⼀次请求⻚⾯时,由服务器把需要的组件或⻚⾯渲染成 HTML字符串,然后把它返回给客户端。客户端拿到⼿的,是可以直接渲染然后呈现给⽤户的 HTML 内容,不需要为了⽣成 DOM 内容⾃⼰再去跑⼀遍 JS 代码。所见即为所得优缺点:
SEO :可以有“现成的内容”拿给搜索引擎看
⾸屏加载速度:服务端渲染模式下,服务器给到客户端的已经是⼀个服务端处理好的可以拿来呈现给⽤户的⽹⻚
缺点: ⾮常吃硬件资源
css、js性能
编码能力