咱们都知道对于Web应用来讲性能很重要。然而性能优化相关的知识却很是的庞大而且杂乱。对于性能优化须要作些什么以及性能瓶颈是什么,一般咱们并不清楚。javascript
不包括那些对性能优化有丰富经验的高手css
事实上关于Web性能有不少能够优化的点,其中涉及到的知识大体能够划分为几类:度量标准、编码优化、静态资源优化、交付优化、构建优化、性能监控。html
图1. 性能优化分类前端
本文主要介绍性能优化须要作的事以及须要考虑的问题。目的在于给读者脑海中生成一个宏观的地图。vue
不会介绍每一个优化项目具体如何操做。PS:后续会有系列文章针对不一样优化分类下的具体优化操做进行更详细的介绍。java
在进行性能优化以前,咱们须要为应用选择一个正确的度量标准(性能指标)以及设定一个合理的优化目标。git
并非全部指标都一样重要,这取决于你的应用。最后根据度量标准设定一个现实的目标。github
下面是一些值得考虑的指标:web
FMP与英雄渲染时间很是类似,但它们不同的地方在于FMP不区份内容是否有用,不区分渲染出的内容是不是用户关心的。面试
想了解更多“性能指标”及更多“Web性能领域的专业术语”能够看个人另外一篇文章:《Web性能领域常见的专业术语》
以上四种指标的设定都有据可循。详细信息请查看RAIL性能模型。
编码优化涉及到应用的运行时性能,本小节介绍几个能够提高程序运行时性能的建议。
事实上数据访问速度有快慢之分,下面列出几个影响数据访问速度的因素:
推荐的作法是缓存对象成员值。将对象成员值缓存到局部变量中会加快访问速度
应用在运行时,性能的瓶颈主要在于DOM操做的代价很是昂贵,下面列出一些关于DOM操做相关提高性能的建议:
下面列出一些流程控制相关的一些能够略微提高性能的细节,这些细节在大型开源项目中大量运用(例如Vue):
for...in
(它能枚举到原型,因此很慢)if-else
和switch
会提高性能Web应用的运行离不开静态资源,因此对静态资源的优化相当重要。
Brotli
或Zopfli
进行纯文本压缩在最高级别的压缩下Brotli会很是慢(但较慢的压缩最终会获得更高的压缩率)以致于服务器在等待动态资源压缩的时间会抵消掉高压缩率带来的好处,但它很是适合静态文件压缩,由于它的解压速度很快。
使用Zopfli压缩能够比Zlib的最大压缩提高3%至8%。
尽量经过srcset
,sizes
和<picture>
元素使用响应式图片。还能够经过<picture>
元素使用WebP格式的图像。
响应式图片可能你们未必据说过,但响应式布局你们确定都据说过。响应式图片与响应式布局相似,它能够在不一样屏幕尺寸与分辨率的设备上都能良好工做(好比自动切换图片大小、自动裁切图片等)。
固然,若是您不知足这种尺度的优化,还能够对图片进行更深层次的优化。例如:模糊图片中不重要的部分以减少文件大小、使用自动播放与循环的HTML5视频替换GIF图,由于视频比GIF文件还小(好消息是将来能够经过img
标签加载视频)。
更多图片优化能够看个人另外一篇文章:《图像优化原理》
交付优化指的是对页面加载资源以及用户与网页之间的交付过程进行优化。
JS的加载与执行会阻塞页面渲染,能够将Script标签放到页面的最底部。可是更好的作法是异步无阻塞加载JS。有多种无阻塞加载JS的方法:defer
、async
、动态建立script
标签、使用XHR异步请求JS代码并注入到页面。
但更推荐的作法是使用defer
或async
。若是使用defer
或async
请将Script标签放到head
标签中,以便让浏览器更早地发现资源并在后台线程中解析并开始加载JS。
Intersection Observer
实现懒加载懒加载是一个比较经常使用的性能优化手段,下面列出了一些经常使用的作法:
Intersection Observer
延迟加载图片、视频、广告脚本、或任何其余资源。延迟加载全部体积较大的组件、字体、JS、视频或Iframe是一个好主意
CSS资源的加载对浏览器渲染的影响很大,默认状况下浏览器只有在完成<head>
标签中CSS的加载与解析以后才会渲染页面。若是CSS文件过大,用户就须要等待很长的时间才能看到渲染结果。针对这种状况能够将首屏渲染必须用到的CSS提取出来内嵌到<head>
中,而后再将剩余部分的CSS用异步的方式加载。能够经过Critical作到这一点。
Resource Hints(资源提示)定义了HTML中的Link元素与dns-prefetch
、preconnect
、prefetch
与prerender
之间的关系。它能够帮助浏览器决定应该链接到哪些源,以及应该获取与预处理哪些资源来提高页面性能。
dns-prefetch
能够指定一个用于获取资源所需的源(origin),并提示浏览器应该尽量早的解析。
<link rel="dns-prefetch" href="//example.com">
复制代码
preconnect
用于启动预连接,其中包含DNS查找,TCP握手,以及可选的TLS协议,容许浏览器减小潜在的创建链接的开销。
<link rel="preconnect" href="//example.com">
<link rel="preconnect" href="//cdn.example.com" crossorigin>
复制代码
Prefetch
用于标识下一个导航可能须要的资源。浏览器会获取该资源,一旦未来请求该资源,浏览器能够提供更快的响应。
<link rel="prefetch" href="//example.com/next-page.html" as="html" crossorigin="use-credentials">
<link rel="prefetch" href="/library.js" as="script">
复制代码
浏览器不会预处理、不会自动执行、不会将其应用于当前上下文。
as
与crossorigin
选项都是可选的。
prerender
用于标识下一个导航可能须要的资源。浏览器会获取并执行,一旦未来请求该资源,浏览器能够提供更快的响应。
<link rel="prerender" href="//example.com/next-page.html">
复制代码
浏览器将预加载目标页面相关的资源并执行来预处理HTML响应。
经过一个现有元素(例如:img
,script
,link
)声明资源会将获取与执行耦合在一块儿。然而应用可能只是想要先获取资源,当知足某些条件时再执行资源。
Preload提供了预获取资源的能力,能够将获取资源的行为从资源执行中分离出来。所以,Preload能够构建自定义的资源加载与执行。
例如,应用可使用Preload进行CSS资源的预加载、而且同时具有:高优先级、不阻塞渲染等特性。而后应用程序在合适的时间使用CSS资源:
<!-- 经过声明性标记预加载 CSS 资源 -->
<link rel="preload" href="/styles/other.css" as="style">
<!-- 或,经过JavaScript预加载 CSS 资源 -->
<script> var res = document.createElement("link"); res.rel = "preload"; res.as = "style"; res.href = "styles/other.css"; document.head.appendChild(res); </script>
复制代码
<!-- 使用HTTP头预加载 -->
Link: <https://example.com/other/styles.css>; rel=preload; as=style
复制代码
PSI(Perceptual Speed Index,感知速度指数)是提高用户体验的重要指标,让用户感受到页面的反馈比没有反馈体验要好不少。
能够尝试使用骨架屏或添加一些Loading过渡动画提示用户体验。
输入响应(Input responsiveness)指标一样重要,甚至更重要。试想,用户点击了网页后缺毫无反应会是什么心情。JS的单线程你们已经不能再熟悉,这意味着当JS在运行时用户界面处于“锁定”状态,因此JS同步执行的时间越长,用户等待响应的时间也就越长。
据调查,JS执行100毫秒以上用户就会明显以为网页变卡了。因此要严格限制每一个JS任务执行时间不能超过100毫秒。
解决方案是能够将一个大任务拆分红多个小任务分布在不一样的Macrotask中执行(通俗的说是将大的JS任务拆分红多个小任务异步执行)。或者使用WebWorkers,它能够在UI线程外执行JS代码运算,不会阻塞UI线程,因此不会影响用户体验。
应用越复杂,主动管理UI线程就越重要
现代前端应用都须要有构建的过程,项目在构建过程当中是否进行了合理的优化,会对Web应用的性能有着巨大的影响。例如:影响构建后文件的体积、代码执行效率、文件加载时间、首次有效绘制指标等。
拿Vue举例,若是您使用单文件组件开发项目,组件会在编译阶段将模板编译为渲染函数。最终代码被执行时能够直接执行渲染函数进行渲染。而若是您没有使用单文件组件预编译代码,而是在网页中引入vue.min.js
,那么应用在运行时须要先将模板编译成渲染函数,而后再执行渲染函数进行渲染。相比预编译,多了模板编译的步骤,因此会浪费不少性能。
Tree-shaking是一种在构建过程当中清除无用代码的技术。使用Tree-shaking能够减小构建后文件的体积。
目前Webpack与Rollup都支持Scope Hoisting
。它们能够检查import
链,并尽量的将散乱的模块放到一个函数中,前提是不能形成代码冗余。因此只有被引用了一次的模块才会被合并。使用Scope Hoisting
可让代码体积更小而且能够下降代码在运行时的内存开销,同时它的运行速度更快。前面2.1节介绍了变量从局部做用域到全局做用域的搜索过程越长执行速度越慢,Scope Hoisting
能够减小搜索时间。
code-splitting
是Webpack中最引人注目的特性之一。此特性可以把代码分离到不一样的bundle
中,而后能够按需加载或并行加载这些文件。code-splitting
能够用于获取更小的bundle
,以及控制资源加载优先级,若是使用合理,会极大影响加载时间。
单页应用须要等JS加载完毕后在前端渲染页面,也就是说在JS加载完毕并开始执行渲染操做前的这段时间里浏览器会产生白屏。
服务端渲染(Server Side Render,简称SSR)的意义在于弥补主要内容在前端渲染的成本,减小白屏时间,提高首次有效绘制的速度。可使用服务端渲染来得到更快的首次有效绘制。
比较推荐的作法是:使用服务端渲染静态HTML来得到更快的首次有效绘制,一旦JavaScript加载完毕再将页面接管下来。
import
函数动态导入模块使用import
函数能够在运行时动态地加载ES2015模块,从而实现按需加载的需求。
这种优化在单页应用中变得尤其重要,在切换路由的时候动态导入当前路由所需的模块,会避免加载冗余的模块(试想若是在首次加载页面时一次性把整个站点所须要的全部模块都同时加载下来会加载多少非必须的JS,应该尽量的让加载的JS更小,只在首屏加载须要的JS)。
使用静态
import
导入初始依赖模块。其余状况下使用动态import
按需加载依赖
正确设置expires
,cache-control
和其余HTTP缓存头。
推荐使用Cache-control: immutable
避免从新验证。
其余一些值得考虑的优化点:
最后,你可能须要一个性能检测工具来持续监视网站的性能。
最后用一张图来总结这篇文章所表达的内容。
更多性能领域的高质量文章请狠狠的点击我!
如今是校招季,阿里巴巴淘系前端正在帮助21届同窗们收割大厂offer。为此,咱们专门建立了交流群,在这里能够提问题,交流面试经验,咱们也会在这里提供简历辅导和答疑解惑等服务,
入群方式: