原文: 本人博客园文章
关注公众号: 微信搜索 前端工具人 ; 收货更多的干货
1、HTTP 优化
- 尽量减少
HTTP请求个数 - 不阻塞情况下异步预先请求;
- 优先使用
get请求, 因为频繁刷新浏览器get请求不会对浏览器、服务器造成太大的压力-- 不需要预检和交互, 无伤; - 合并请求 / 常用数据缓存代替;
- 使用
http缓存, 例如:输出图片、execl表格、pdf、音频、视频等- 原理:
http缓存都是在第二次请求开始的,第一次服务器会在资源返回的响应中携带上四个常用的响应头,浏览器会通过判别这些响应值来决定资源缓存的状态,再次请求的时候浏览器会带上这些响应头; Cache-Control(强缓存)- 可以携带多个响应值,这些值可以设置缓存时间、状态以及验证状态;
public: 所有内容都将被缓存包括客户端、代理cdn节点private: 只缓存到客户端,不缓存到代理服务器no-cache: 需要先与服务器确认no-store: 所有内容都不被缓存max-age: 在多少秒之后失效
Expires(强缓存)- 标记了数据的过期时间,超过其中规定的时间后,缓存会被定义为过期,优先级
Cache-Control的max-age > Expires
- 标记了数据的过期时间,超过其中规定的时间后,缓存会被定义为过期,优先级
ETag(协商缓存)--> 值是一个字符串(数据的哈希值),每个数据都有一个单独的标志- 浏览器会在后续的请求中携带上这个参数来确定缓存是否需要更新;
- 需要注意的是,
ETag只有在本地缓存已过期(Expires)或者缓存模式设置为no-cache(Cache-Control)的时候,才会被浏览器携带上服务器端的值进行判别;
Last-Modified(协商缓存)- 向浏览器发送一个数据上次被修改的时间;
- 浏览器就知道了该数据最后被修改的时间,后续请求中,会和服务器进行时间的比较,如果服务器上的时间比本地时间要新,说明数据有更改,浏览器需要重新下载数据;
- 缺点:当服务器响应中有
Expires或者Cache-Control设置了max-age响应头的时候,浏览器不会向服务器发起校验请求,而是直接复用本地缓存。如果此时服务器进行了资源的更新,用户就无法获取到最新的资源,只能通过强制刷新浏览器缓存来跟服务器请求最新的资源
- 缓存的位置按照获取资源请求优先级,缓存位置依次如下:
Memory Cache(内存缓存) 是浏览器最先尝试命中的缓存,也是响应最快的缓存。但是存活时间最短的,当进程结束后,tab 标签关闭后,缓存就不存在了,因为内存空间比较小,通常较小的资源放在内存缓存中,比如 base64 图片等资源Service Worker(离线缓存) Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。Disk Cache(磁盘缓存) 内存的优先性,导致大文件不能缓存到内存中,那么磁盘缓存则不同。虽然存储效率比内存缓存慢,但是存储容量和存储市场有优势。- Push Cache(推送缓存)它是最后一道缓存
- 原理:
2、使用 CDN 服务器端缓存加快访问速度 (俗称边缘计算)
- 原理:
CDN网络是在用户和服务器之间增加了一层缓存层,将用户的请求引导到最优的缓存节点就近获取所需要的内容而不是服务器源站,从而降低网络用塞、加块访问速度响应用户的请求 (俗称负载均衡); - 过程:先向
CDN边缘节点发起请求 -> 检测是否过期 -> 没有直接返回 -> 过期则去根服务器获取数据再返回; - 设置:通过
http响应头中的Cache-Control和max-age的字段来设置CDN边缘节点的数据缓存时间; - 例子:网站中大量的
css,html,js等文件、大文件的下载(图片、视频、音频等),将这些静态内容推送到CDN节点; - 构成:初始服务器,分布于各个节点的缓存服务器,重定向DNS服务器和内容交换服务器
- 主要技术:
-
- 负载均衡;
-
- 内容存储技术 (内容源的存储、内容在
cache节点中的分布式存储);
- 内容存储技术 (内容源的存储、内容在
-
- 内容分发技术(构建网络,将链接到
IP网络上的内容,快速的传输到用户终端)
- 内容分发技术(构建网络,将链接到
-
- 缺点:当源服务器资源更新后,如果
CDN节点上缓存数据还未过期,用户访问到的依旧是过期的缓存资源,这会导致用户最终访问出现偏差。因此,开发者需要手动刷新相关资源,使CDN缓存保持为最新的状态
3. 减少 DNS 查找次数
- DNS用于映射主机名和
IP地址,DNS解析有代价,一般一次解析需要20~120毫秒。浏览器在DNS查询完成前不会下载任何东西,所以浏览器会想办法对DNS的查找结果进行缓存 - 减少域名主机可减少DNS查询的次数,最理想的方法就是将所有的内容资源都放在同一个域(
Domain)下面,这样访问整个网站就只需要进行一次DNS查找,这样可以提高性能。 - 在
HTTP /1.1中放在同个域下面会带来一定数量的并行度(它的建议是2),那么就会出现下载资源时的排队现象,这样就会降低性能,推荐客户端针对每个域在一个网站里面使用至少2个域,但不多于4个域
4. 图片 优化
- 相对应的切换优先使用雪碧图
- 优先使用
font字体、svg、base64、JPG、JPEG、WEBP格式的图片 - 列表图片使用预加载、懒加载、及脱离文档流后进行DOM回收
- 使用
http、cdn缓存、不失帧情况下对图片压缩
5. 渲染 优化
SSR(优化首页渲染时间)、骨架屏、开启gzip压缩、js混淆(无效字符及注释的删除、码语义的缩减和优化)、css压缩、合并css资源- 减少重定向、减少外链、不滥用
web字体、css的文件放在头部,js文件放在尾部或者异步(async和defer、动态脚本创建) ( 标签preload渲染前加载,prefetch,dns-prefetch渲染完成后空闲时间加载 ) - 尽量避免內联样式、避免
html里执行js - 尽量使用
css动画、减少css表达式、使用requestAnimationFrame操作动画
6. DOM 优化
- 避免重绘重排、减少
DOM元素个数 - 批量操作
DOM,并且脱离文档流后在操作 - 多次修改样式、结构。尽量合并在一起修改
- 使用
css3 GPU硬件加速,translate3d、translateZ、rotate、scale、transform、opacity、filters等动画效果不会引起回流重绘 - 对于频繁操作(例如:输入框、
scroll监听)使用节流、防抖- 节流:短时间内大量触发同一事件,在函数执行一次之后,该函数在指定的时间期限内不再执行,直至过了这段时间才重新生效(例如监听滚动条)
- 防抖:如果短时间内大量触发同一事件,只会执行一次函数(例如:input事件)
7. 使用JSON格式
JSON是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式。同时,JSON是 JavaScript原生格式,这意味着在 JavaScript 中处理 JSON数据不需要任何特殊的 API 或工具包
8. 控制Cookie大小和污染
Cookie是本地的磁盘文件,每次浏览器都会去读取相应的Cookie,所以建议去除不必要的Coockie,使Coockie体积尽量小- 使用
Cookie跨域操作时注意在适应级别的域名上设置coockie以便使子域名不受其影响 Cookie是有生命周期的,所以请注意设置合理的过期时间,合理地Expire时间和不要过早去清除coockie
9. 常规
避免 404、减少 DOM 访问、用 <link> 代替 @import、保持单个内容小于25K
10. vue 优化
v-if(从DOM树中删除、成本大吗,会重绘重排) 和v-show(适应于频繁的操作显示隐藏)computed(值有缓存,只有它依赖的属性值发生改变 和watch(对数据的监听回调,应做防抖节流操作) 区分使用场景v-for遍历必须为item添加key,且避免同时使用v-if- 长列表性能优化 (
Vue会对数据进行劫持,实现双向数据绑定,但有的时候就说纯粹的数据展示,所以应避免vue静态数据进行劫持,Object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了) 事件的销毁、路由懒加载、第三方插件的按需引入 - 服务端渲染
SSR (vue-server-renderer)或者 预渲染(prerender-spa-plugin)prerender-spa-plugin利用了Puppeteer的爬取页面的功能,Puppeteer是一个Chrome官方出品的headlessChromenode库,在Webpack构建阶段的最后,在本地启动一个Puppeteer的服务,访问配置了预渲染的路由,然后将Puppeteer中渲染的页面输出到HTML文件中,并建立路由对应的目录assetsPublicPath原先写的是/dist,导致body渲染不出来,改成/- 插件在
webpack此"webpack": "^4.6.0"版本下不支持路由懒加载,4.28.4版本才修复 - 预渲染只能保证静态部分不更改, 要动态数据的话,
webpack的devserver代理数据无效,需要用nginx或者其他代理工具代理线上数据
vue-server-renderer利用服务端渲染,当你看的页面的时候,实际页面已经有服务端帮你渲染完成直接返回给浏览器- 提高了用户体验,能快速的浏览的所需页面、
SEO友好 - 减轻了浏览器压力,但相应的造成了服务器的压力;
- 开发成本,需要熟悉
node.js、部署等后端知识。只能使用beforeCreated、created两个生命周期函数
- 提高了用户体验,能快速的浏览的所需页面、
- 缺点:
- 在数据预获取阶段注册的钩子函数中,最好只进行数据的获取和保存,不进行其他任何涉及
this的操作。因为此时的this是服务端的this,是所有用户共享的this,进行操作将发生一些不可预知的错误 Vuex在服务端预获取数据,Vuex应使用惰性注册的方案,避免初始化实例的时候就把所有的模块统一注册,将会出现多个页面共用许多模块的问题- 需要手动拿到客户端的
cookie传给后端服务器cookie穿透 - 路由模式:
ssr的路由需要采用history的方式。hash模式的路由提交不到服务器上 DDOS攻击:利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应(解决:在服务端不做过多复杂的数据处理、DDOS软硬件防火墙)sql注入:对参数进行校验进行避免;- 数据泄露:使用
vuex的情况下,如果不使用惰性加载,容易造成数据泄露的情况发生(全局的this)。
- 在数据预获取阶段注册的钩子函数中,最好只进行数据的获取和保存,不进行其他任何涉及
11. React 优化
(性能主要耗费在于update阶段的diff算法,因此性能优化也主要针对diff算法)
- 减少
diff算法触发次数(实际上减少update流程的次数)setState机制是批更新策略,已经降低了update过程的触发次数,尽量无论数据处理多么复杂,保证最后只调用一次setState- 父组件
render父组件的render必然会触发子组件进入update阶段(无论props是否更新)。尽量在shouldComponentUpdate / PureComponent方法处理, 最常见的方式为进行this.props和this.state的浅比较来判断组件是否需要更新( 浅判等 只会比较到两个对象的 ownProperty 是否符合Object.is()判等,不会递归地去深层比较) forceUpdate调用后将会直接进入componentWillUpdate阶段,无法拦截,因此在实际项目中应该弃用shouldComponentUpdate是决定react组件什么时候能够不重新渲染的函数, 减少不必要的props变化导致的渲染。如一个不用于渲染的props导致的update
- 正确使用
diff算法- 不使用跨层级移动节点的操作
- 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点
- 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。
12. Webpack 优化
Webpack对图片进行压缩:image-webpack-loader 插件- 减少 ES6 转为 ES5 的冗余代码
babel-plugin-transform-runtime / Tree-Shaking插件, 修改.babelrc配置plugins: "transform-runtime" - 模板预编译
vue-template-loader插件 - 优化
SourceMap- 开发环境推荐:
cheap-module-eval-source-map - 生产环境推荐:
cheap-module-source-map
- 开发环境推荐:
- 构建结果输出分析:
webpack-bundle-analyzer 插件 - 使用CDN加速静态资源加载
output: 配置存放静态文件的CDN地址 - 给 loader 减轻负担
用
include或exclude来帮我们避免不必要的转译module --> rules配置; 开启缓存将转译结果缓存至文件系统loader: 'babel-loader?cacheDirectory=true' - 使用
Happypack将loader由单进程转为多进程webpack的缺点是单线程的,我们可以使用Happypack把任务分解给多个子进程去并发执行,大大提升打包效率。配置的方法是把loader的配置转移到HappyPack中去。 DllPlugin提取公用库 开发过程中,我们经常需要引入大量第三方库,这些库并不需要随时修改或调试,我们可以使用DllPlugin和DllReferencePlugin单独构建它们,配置webpack.dll.config.jsexternals选项 以使用externals让webpack不打包某部分,然后在其他地方引入 cdn 上的 js 文件,利用缓存下载 cdn 文件达到减少打包时间的目的。webpack.prod.config.js配置externals选项- 构建体积压缩
可以使用
vue-cli4或者webpack-bundle-analyzer生成构建统计报告- 代码分割
splitChunks- 业务代码和第三方库分离打包,实现代码分割;
- 业务代码中的公共业务模块提取打包到一个模块;
- 第三方库最好也不要全部打包到一个文件中,因为第三方库加起来通常会很大,我会把一些特别大的库分别独立打包,剩下的加起来如果还很大,就把它按照一定大小切割成若干模块。
- 删除冗余代码 -->
Tree-Shaking插件 - UI 库 按需加载 -->
babel-plugin-component插件 - 懒加载 -->
@babel/plugin-syntax-dynamic-import插件
- 代码分割
// webpack-bundle-analyzer 配置
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
plugins: [new BundleAnalyzerPlugin()],
};
- 开启gzip压缩
//nginx配置开启gzip压缩,nginx会根据配置情况对指定的类型文件进行压缩。主要针对js与css。如果文件路径中存在与原文件同名(加了个.gz),nginx会获取gz文件,如果找不到,会主动进行gzip压缩
// nginx 配置
gzip on; #开启或关闭gzip on off
gzip_disable "msie6"; #不使用gzip IE6
gzip_min_length 100k; #gzip压缩最小文件大小,超出进行压缩(自行调节)
gzip_buffers 4 16k; #buffer 不用修改
gzip_comp_level 8; #压缩级别:1-10,数字越大压缩的越好,时间也越长
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 压缩文件类型
gzip_vary off;
13. Chrome Performance
多使用 Chrome Performance 的火焰图 查找性能瓶颈(评测报告中FP、FCP、FMP、LCP、TTI、TTFB、FCI、FID、DCL、Speed Index)
FP"首次绘制" 是第一个“时间点”,它代表浏览器第一次向屏幕传输像素的时间,就是页面在屏幕上首次发生视觉变化的时间。FCP"首次内容绘制", 代表浏览器第一次向屏幕绘制 “内容” (只有首次绘制文本、图片(包含背景图)、非白色的canvas或SVG时才被算作FCP)FP和FCP可能是相同的时间,也可能是先FP后FCP。FMP"首次有效绘制" 主要内容”开始出现在屏幕上的时间点。它是我们测量用户加载体验的主要指标LCP可视区“内容”最大的可见元素开始出现在屏幕上的时间点。TTI"可交互时间" 网页第一次 完全达到可交互状态 的时间点TTFB表示浏览器接收第一个字节的时间FCI告诉我们页面什么时候完全达到可用FIDFID指的是用户首次与产品进行交互时,我们产品可以在多长时间给出反馈DCLDomContentloaded事件触发的时间Speed Index页面可见部分的平均时间- 商城、官网、博客这种页面更侧重FMP(用户希望尽快看到有价值的内容),而类似后台管理系统或在线PPT这种产品则更侧重
TTI(用户希望尽快与产品进行交互)。