在互联网发展的当下,webApp 项目越来越大,需求越来越繁重,功能越来越大,导致代码打包体积越来越大,
页面的打开速度,页面的流畅度 ,用户体验也是一项对产品的考核标准之一,性能优化也是前端开发的必要工作。
对于性能优化我们 通过工程化、加载、代码优化等方面分开讲解。
性能优化核心 就是”小“字为先
工程化
打包
资源压缩
代码压缩可以减少代码体积,节约带宽,提高下载速度
在线压缩工具
- 压缩:删除 Javascript 代码中所有注释、跳格符号、换行符号及无用的空格,从而压缩 JS 文件大小。
- 混淆:经过编码将变量和函数原命名改为毫无意义的命名,以防止他人窥视和窃取 Javascript 源代码。
webpack 配置压缩
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [ // 用于配置 minimizers 和选项
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
tree shaking
tree-shaking可以理解为通过工具"摇"我们的JS文件,将其中用不到的代码"摇"掉,是一个性能优化的范畴
- webpack4 通过设置mode就可以支持 tree shaking
module.exports = {
mode: 'production',
}
注:webpack自身的Tree-shaking不能分析副作用的模块
webpack-deep-scope-plugin: 这个插件主要用于填充webpack4自身Tree-shaking的不足,通过作用域分析来消除无用的代码 在线演示demo
webpack5 tree shaking
module.exports = {
optimization: {
usedExports: true, // 识别无用代码
minimize: true, // 将无用代码在打包中删除
concatenateModules: true, // 尽可能将所有模块合并输出到一个函数中
}
}
-
eslint-plugin-you-dont-need-momentjs
如果你正在使用ESLint,你可以安装一个插件来帮助你识别你代码库中不需要(可能不需要)Moment.js的地方。
按需加载
首屏的加载很重要,对与SPA的项目来说,如果是大文件,使用按需加载就十分合适。比如一个近1M的全国城市省市县的js文件,在我首屏加载的时候并不需要引入,而是当用户点击选项的时候才加载。如果不点击,则不会加载。就可以缩短首屏http请求的数量以及时间。
代码拆分
在互联网发展的当下,webApp 项目越来越大,文件体积过大是很影响性能的一项。特别是对于移动端的设备而言简直是灾难。
此外对于某些只要特定环境下才需要的代码,一开始就加载进来显然也不那么合理,这就引出了按需加载的概念了。为了解决这些情况,代码拆分就应运而生了。代码拆分故名思意就是将大的文件按不同粒度拆分,以满足解决生成文件体积过大、按需加载等需求。
webpack代码拆分
webpack通过一下三种方式实现了代码拆分方式
- 多入口分开打包
- 去重,抽离公共模块和第三方库
- 动态加载
clean-css
uncss( 去除无用的 css)Simply UnCSS your styles online!
加载优化
关于加载优化,我们首先需要明确几个浏览器的重要性能指标,参考可参考 performance,
- TTFB(Time to First Byte): 表示浏览器接收第一个字节的时间
- FP(First Paint):页面的反应,第一个像素点落地 background:#ddd;看的见
页面在导航后首次呈现出不同于导航前内容的时间点.当浏览器开始渲染页面,白屏触发,这时候你如果设置了背景颜色的话,就可以看到页面出现了背景色 - FCP(First Contentful Paint):首次绘制页面“主要内容”的时间点。
- FMP(First Meaningful Paint):首次绘制页面“主要内容”的时间点。 有意义的绘制 (自定义的)
- DCL(DOMContentLoaded): 表示 HTML 加载完成事件, L(onLoad) 表示页面所有资源加载完成事件
- LCP(Largest Contentful Paint):第一个绘制的最大内容 可视区域“内容”最大的可见元素开始出现在页面上的时间点。
- CLS(Cumulative Layout Shift): 表示用户经历的意外 layout 偏移的频率。
- TBT(Total Blocking Time): 表示从 FCP 到 TTI 之间,所有 long task 的阻塞时间之和
- TTi(Time to Interactive):页面可交互
performance timing
performance.timing:是一系列关键时间点,它包含了网络、加载、解析等一系列的时间数据。
通过下图来解析下各个关键时间点的含义如下所示:
- navigationStart 浏览器窗口的前一个网页关闭时发生 unload 事件时的 Unix 时间戳,属于最前的测量时间点
- unloadEventStart 前网页与当前网页同属一个域名时,返回前一个网页的 unload 事件发生时的 Unix 时间戳
- unloadEventEnd 前网页与当前网页同属一个域名时,返回前一个网页 unload 事件的回调函数结束时的 Unix 时间戳
- redirectStart 返回第一个 HTTP 跳转开始时的 Unix 时间戳
- redirectEnd 返回最后一个 HTTP 跳转结束时的 Unix 时间戳
- fetchStart 返回浏览器准备使用 HTTP 请求读取文档等资源时的 Unix 时间戳,在网页查询本地缓存之前发生
- domainLookupStart 返回域名查询开始时的 Unix 时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于 fetchStart 属性的值
- domainLookupEnd 返回域名查询结束时的 Unix 毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于 fetchStart 属性的值
- connectStart 返回 HTTP 请求开始向服务器发送时的 Unix 毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于 fetchStart 属性的值
- connectEnd 返回浏览器与服务器之间的连接建立时的 Unix 毫秒时间戳。如果建立的是持久连接,则返回值等同于 fetchStart 属性的值。连接建立指的是所有握手和认证过程全部结束
- secureConnectionStart 返回浏览器与服务器开始安全链接的握手时的 Unix 毫秒时间戳。如果当前网页不要求安全连接,则返回 0
- requestStart 返回浏览器向服务器发出 HTTP 请求时(或开始读取本地缓存时)的 Unix 毫秒时间戳
- responseStart 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的 Unix 毫秒时间戳
- responseEnd 返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前 HTTP 连接已经关闭,则返回关闭时)的 Unix 毫秒时间戳
- domLoading 返回当前网页 DOM 结构开始解析时(即 Document.readyState 属性变为“loading”、相应的 readystatechange 事件触发时)的 Unix 毫秒时间戳
- domInteractive 返回当前网页 DOM 结构结束解析、开始加载内嵌资源时(即 Document.readyState 属性变为“interactive”、相应的 readystatechange 事件触发时)的 Unix 毫秒时间戳
- domContentLoadedEventStart 返回当前网页 DOMContentLoaded 事件发生时(即 DOM 结构解析完毕、所有脚本开始运行时)的 Unix 毫秒时间戳
- domContentLoadedEventEnd 返回当前网页所有需要执行的脚本执行完成时的 Unix 毫秒时间戳
- domComplete 返回当前网页 DOM 结构生成时(即 Document.readyState 属性变为“complete”,以及相应的 readystatechange 事件发生时)的 Unix 毫秒时间戳
- loadEventStart 返回当前网页 load 事件的回调函数开始时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0
- loadEventEnd 返回当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0
一个网页加载的时间周期
关键的指标性能计算
- 上个页面的到这个页面的时长 fetchStartfet-navigationStart
- 重定向时常 : redirectEnd-redirectStart
- DNS 查询耗时 :domainLookupEnd - domainLookupStart
- TCP 链接耗时 :connectEnd - connectStart
- request 请求耗时 :responseEnd - responseStart
- 解析 dom 树耗时 : domComplete - domInteractive
- 白屏时间 :responseStart - navigationStart
- domready 时间(用户可操作时间节点) :domContentLoadedEventEnd - navigationStart
- onload 时间(总下载时间) :loadEventEnd - navigationStart
优化的时间可参考web.dev
网络
QPS即每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
QPS = 并发量 / 平均响应时间
合理计算 QPS 未雨绸缪
开启 cnd 加速(并行最多 5 个)。节约 cookie 带宽 节约主域名的连接数,优化页面响应速度
开启页面懒加载
缓存静态资源文件 localstrage
nginx:
nginx 开启 gzip 压缩 etag expires 缓存
nginx 开启 反向代理 负载均衡
(html webpsack plugin)
preload 提前加载
prefetch 预判加载
Preconnect 预解析
DNS预解析
DNS
域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。
dns-prefetch域名解析:从域名查询IP的过程,这个过程一般都很快的,但也会引起延迟。一般浏览器会适当的对解析结果缓存,并对页面中出现的新域名进行预解析,但并不是所有的浏览器都会这么做,为了帮助其它浏览器对某些域名进行预解析,你可以在页面的html标签中添加dns-prefetch告诉浏览器对指定域名预解析
典型的一次DNS解析需要耗费 20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式
DNS解析方式
浏览器对网站第一次的域名DNS解析查找流程依次为:浏览器缓存——系统缓存——路由器缓存——ISP DNS缓存——递归搜索
<!--用meta信息来告知浏览器, 当前页面要做DNS预解析-->
<meta http-equiv="x-dns-prefetch-control" content="on" />
<!--在页面header中使用link标签来强制对DNS预解析-->
<link rel="dns-prefetch" href="https://www.baidu.com" />
CDN
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术
缓存刷新
源站内容更新后,希望用户可以获取到最新资源,CDN 租户可以通过提交刷新请求将 CDN 节点上指定的缓存内容强制过期。当用户再次访问时,CDN 节点将回源获取已更新内容返回给用户并在节点缓存最新资源。(简单来说就是删除 cdn 各节点上的缓存,有用户获取文件的时候,直接回源取文件!)
缓存预热
提交指定资源的缓存预热请求后,对应源站资源将分发到 CDN 节点,当用户发起访问请求时,可以直接从 CDN 节点获取,有效地降低了回源率。(简单来说就是直接从源站下发文件到 cdn 各节点上的缓存,有用户获取文件的时候就可以直接取到最新文件!)
HTTP
keep-alive
在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。在后来的HTTP/1.0中以及HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,下一次咱们还用这个请求继续交流。协议规定HTTP/1.0如果想要保持长连接,需要在请求头中加上Connection: keep-alive,而HTTP/1.1默认是支持长连接的,有没有这个请求头都行
http2.0
HTTP2.0大幅度的提高了web性能,在HTTP1.1完全语义兼容的基础上,进一步减少了网络的延迟。实现低延迟高吞吐量。对于前端开发者而言,减少了优化工作,http2.0请求demo。
http2.0优势
- 二进制分帧:在应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层,HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 。
- 首部压缩:HTTP/2 使用了专门为首部压缩而设计的 HPACK 算法,达到请求头压缩的目的。
- 多路复用:多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息
- 请求优先级:把HTTP消息分为很多独立帧之后,就可以通过优化这些帧的交错和传输顺序进一步优化性能
- 服务器推送:服务端推送是一种在客户端请求之前发送数据的机制
Nginx
nginx:是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
缓存
强缓存
Expires: 头能有效的利用浏览器的缓存能力来改善页面的性能,能在后续的页面中有效避免很多不必要的Http请求,WEB服务器使用Expires头来告诉Web客户端它可以使用一个组件的当前副本,直到指定的时间为止,Expires有一个非常大的缺陷,它使用一个固定的时间,要求服务器与客户端的时钟保持严格的同步,并且这一天到来后,服务器还得重新设定新的时间。
Cathe-Control:http1.1 引入,它使用max-age指定组件被缓存多久,从请求开始在max-age时间内浏览器使用缓存,之外的使用请求,这样就可以消除Expires的限制,
注: Cache-Control(1.1版本) 的优先级高于 Expires(1.0 版本)
协商缓存
etag:数据签名,资源内容会对应有一个唯一的签名(sha1),如果资源数据更改,签名也会变。配合If-Match或者If-None-Match使用,
last-modified:上次修改时间(精确到秒),配合If-Modified-Since或If-Unmodified-Since使用,通常浏览器使用If-Modified-Since
浏览器缓存流程图
开启gzip
GZIP是若干文件压缩程序的简称,通常指GNU计划的实现,此处的GZIP代表的就是GUN ZIP,这也是HTTP1.1协议定义的两种压缩方法中最常用的一种压缩方法,客户端浏览器大都支持这种压缩格式。
gzip on;
gzip_static on; //静态资源
gzip_vary on; //是否在 http header 中添加 Vary: Accept-Encoding,建议开启
gzip_comp_level 5; //(建议) gzip 压缩比,1 压缩比最小处理速度最快,9 压缩比最大但处理最慢(传输快但比较消耗 cpu)
gzip_min_length 0 ; //默认值是 0,不管页面多大都压缩。 建议设置成大于 1k 的字节数,小于 1k 可能会越压越大
gzip_http_version 1.1; //版本信息
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
负载均衡
upstream web_mgrsys {
server 127.0.0.1:8090 weight=10;
server 127.0.0.1:3000 weight=3;
}
proxy_pass http://web_mgrsys;
可参考 幸福拾荒者文档
预加载
prefetch: 它的作用是告诉浏览器加载下一页面可能会用到的资源,注意,是下一页面,而不是当前页面。因此该方法的加载优先级非常低,也就是说该方式的作用是加速下一个页面的加载速度
-
preload: 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),需要执行时再执行
1、将加载和执行分离开,不阻塞渲染和document的onload事件
2、提前加载指定资源,不再出现依赖的font字体隔了一段时间才刷出的情况
async & defer
蓝色线代表网络读取,红色线代表执行时间,绿色线代表 HTML 解析。
- async:加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
- defer:加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成,并且多个 defer 会按照顺序进行加载。
代码优化
雅虎军规
雅虎军规:无论是在工作中,还是在面试中,web 前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化 34 条军规,不过现在已经是 35 条了,所以可以说是雅虎前端优化的 35 条军规。已经分类,这样对于优化有一个比较清晰的方向。
js
js优化方案
- 精简 js 代码 巧用数据结构与算法
- 避免内存泄漏,避免全局变量、闭包、降低循环
- 脱离的 dom 元素及时清理其绑定的事件
css
Reflow (重排)
当涉及到DOM节点的布局属性发生变化时,就会重新计算该属性,浏览器会重新描绘相应的元素,此过程叫 回流(Reflow)
Repaint(重绘)
当影响DOM元素可见性的属性发生变化 (如 color) 时, 浏览器会重新描绘相应的元素, 此过程称为 重绘(Repaint)。因此重排必然会引起重绘。
重排触发机制
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素本身的尺寸发生改变
- 内容改变
- 页面渲染器初始化
- 浏览器窗口大小发生改变
重绘触发机制
- 背景颜色样式修改
重绘&重排优化方式
- 对需要修改的dome,尽量使用文档片段一次插入页面
- 对动画尽量使用css3 动画,开启动画的GPU加速,把渲染计算交给GPU。
本文原创发布于慕课网 ,转载请注明出处,谢谢合作 - 将需要多次重排的元素,position属性设为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素
css 优化方案
- 降低层级复杂度 减少css层级嵌套
- 降低渲染阻塞
- 使用css3动画 开启3d加速,减少动画对页面的重排重绘
- contain:layout
- 隐藏元素使用display为none,
- 将需要多次重排的元素,position属性设为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素
html
- iframe 延迟加载
- 压缩删除注释 html-minifier :HTMLMinifier is a highly configurable, well-tested, JavaScript-based HTML minifier.
- 降低节点过渡嵌套
- css javascript 尽量外链
font
字体优化方式
font-display:auto
auto:默认值。典型的浏览器字体加载的行为会发生,也就是使用自定义字体的文本会先被隐藏,直到字体加载结束才会显示。著作权归作者所有。
- block: 阻塞等字体下载完成在显示 3s
- swap:默认字体-后期替换
- Fallback:不显示
- optional:移动端 默认 或者自定义
图片
1.tiny 在线压缩图片工具
https://tinyjpg.com/
图片 imagemin
渐进式图片方式:baseline jpeg 一次将图像由左到右、由上到下顺序处理。当您的 JPEG 图像低于 10K 时,最好保存为基本 JPEG(估计有 75%的可能性会更小)
Progressive jpeg 当图像传输的时间较长时,可将图像分数次处理,以从模糊到清晰的方式来传送图像(效果类似 GIF 在网络上的传输)。
图片优化npm依赖包
Progressive-image
: A dead simple progressive-image module for Vanilla JavaScript and Vue.js 1.0+ & 2.0+ progressive-image-demoImageMagick: 是一套功能强大、稳定而且开源的工具集和开发包,可以用来读、写和处理超过 89 种基本格式的图片文件,包括流行的 TIFF、JPEG、GIF、 PNG、PDF 以及 PhotoCD 等格式。利用 ImageMagick,你可以根据 web 应用程序的需要动态生成图片, 还可以对一个(或一组)图片进行改变大小、旋转、锐化、减色或增加特效等操作,并将操作的结果以相同格式或其它格式保存,对图片的操作,即可以通过命令行进行,也可以用 C/C++、Perl、Java、PHP、Python 或 Ruby 编程来完成。同时 ImageMagick 提供了一个高质量的 2D 工具包,部分支持 SVG。ImageMagic 的主要精力集中在性能,减少 bug 以及提供稳定的 API 和 ABI 上。
jpeg-recompress:Compress JPEGs by re-encoding to the smallest JPEG quality while keeping perceived visual quality the same and by making sure huffman tables are optimized
imagemin:Minify images seamlessly