diff --git a/docs/front-end-engineering/performance.md b/docs/front-end-engineering/performance.md index 8b905aec..fb53d616 100644 --- a/docs/front-end-engineering/performance.md +++ b/docs/front-end-engineering/performance.md @@ -406,6 +406,60 @@ module.exports = { 1. 使用非常繁琐 2. 如果第三方库中包含重复代码,则效果不太理想 +**详解dllPlugin** + + +DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin的使用方法如下: + +```js +// 单独配置在一个文件中 +// webpack.dll.conf.js +const path = require('path') +const webpack = require('webpack') +module.exports = { + entry: { + // 想统一打包的类库 + vendor: ['react'] + }, + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].dll.js', + library: '[name]-[hash]' + }, + plugins: [ + new webpack.DllPlugin({ + // name 必须和 output.library 一致 + name: '[name]-[hash]', + // 该属性需要与 DllReferencePlugin 中一致 + context: __dirname, + path: path.join(__dirname, 'dist', '[name]-manifest.json') + }) + ] +} +``` + +然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中 + +```js +// webpack.conf.js +module.exports = { + // ...省略其他配置 + plugins: [ + new webpack.DllReferencePlugin({ + context: __dirname, + // manifest 就是之前打包出来的 json 文件 + manifest: require('./dist/vendor-manifest.json'), + }) + ] +} +``` + +可以通过一些小的优化点来加快打包速度 + +- resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面 +- resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径 +- module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助 + #### 2.2 自动分包(会降低构建效率,开发效率提升,新的模块不需要手动处理了) 1. 基本原理 @@ -843,96 +897,866 @@ plugins: [ #### 2.7 按需加载 -#### 2.8 DllPlugin +在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 lodash 这种大型类库同样可以使用这个功能。 + +按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 Promise,当 Promise 成功以后去执行回调。 + + +#### 2.8 其他 + +- 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css +- 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径 +- Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存 +- 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码 + +提取第三方库 vendor: +这是也是 webpack 大法的 code splitting,提取一些第三方的库,从而减小 app.js 的大小。 +代码层面做好懒加载,网络层面把 CDN、本地缓存用好,前端页面问题基本解决一大半了。剩下主要就是接口层面和“视觉上的快”的优化了,骨架屏先搞起,渲染一个“假页面”占位;接口该合并的合并,该拆分的拆分,如果是可滚动的长页面,就分批次请求 + +**总结:如果有一个工程打包特别大-如何进行优化?** +1. CDN 如果工程中使用了一些知名的第三方库,可以考虑使用 CDN,而不进行打包 +2. 抽离公共模块 如果工程中用到了一些大的公共库,可以考虑将其分割出来单独打包 +3. 异步加载 对于那些不需要在一开始就执行的模块,可以考虑使用动态导入的方式异步加载它们,以尽量减少主包的体积 +4. 压缩、混淆 +5. tree shaking 尽量使用 ESM 语法进行导入导出,充分利用 tree shaking 去除无用代码 +6. gzip 开启 gzip 压缩,进一步减少包体积 +7. 环境适配 有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。 + + +### 3. 运行性能 + +运行性能是指,JS代码在浏览器端的运行速度,它主要取决于我们如何书写高性能的代码 + +永远不要过早的关注于性能,因为你在开发的时候,无法完全预知最终的运行性能,过早的关注性能会极大的降低开发效率 +性能优化主要从上面三个维度入手,性能优化没有完美的解决方案,需要具体情况具体分析 + +### 4. webpack5 内置优化 -#### 2.9 其他 +1. **[webpack scope hoisting](https://webpack.docschina.org/plugins/module-concatenation-plugin/)** +scope hoisting 是 webpack 的内置优化,它是针对模块的优化,在生产环境打包时会自动开启。 +在未开启scope hoisting时,webpack 会将每个模块的代码放置在一个独立的函数环境中,这样是为了保证模块的作用域互不干扰。 +而 scope hoisting 的作用恰恰相反,是把多个模块的代码合并到一个函数环境中执行。在这一过程中,webpack 会按照顺序正确的合并模块代码,同时对涉及的标识符做适当处理以避免重名。 +这样做的好处是减少了函数调用,对运行效率有一定提升,同时也降低了打包体积。 +但 scope hoisting 的启用是有前提的,如果遇到某些模块多次被其他模块引用,或者使用了动态导入的模块,或者是非 ESM 的模块,都不会有 scope hoisting。 +2. 清除输出目录 +`webpack5`清除输出目录开箱可用,无须安装`clean-webpack-plugin`,具体做法如下: + +```javascript +module.exports = { + output: { + clean: true + } +} +``` + +3. top-level-await + +`webpack5`现在允许在模块的顶级代码中直接使用`await` + +```javascript +// src/index.js +const resp = await fetch("http://www.baidu.com"); +const jsonBody = await resp.json(); +export default jsonBody; +``` + +目前,`top-level-await`还未成为正式标准,因此,对于`webpack5`而言,该功能是作为`experiments`发布的,需要在`webpack.config.js`中配置开启 + +```javascript +// webpack.config.js +module.exports = { + experiments: { + topLevelAwait: true, + }, +}; +``` + +4. 打包体积优化 + +`webpack5`对模块的合并、作用域提升、`tree shaking`等处理更加智能 + +```javascript +// webpack.config.js +module.exports = { + mode: "production", + devtool: "source-map", + entry: { + index1: "./src/index1.js", + index2: "./src/index2.js", + }, +}; + +``` + +![](../public/front-end-engineering/2024-01-28-17-09-16.png) + +![](../public/front-end-engineering/2024-01-28-17-09-22.png) + +5. 打包缓存开箱即用 + +在`webpack4`中,需要使用`cache-loader`缓存打包结果以优化之后的打包性能 + +而在`webpack5`中,默认就已经开启了打包缓存,无须再安装`cache-loader` + +默认情况下,`webpack5`是将模块的打包结果缓存到**内存**中,可以通过`cache`配置进行更改 + +```javascript +const path = require('path'); + +module.exports = { + mode: 'development', + devtool: 'source-map', + entry: './src/index.js', + cache: { + type: 'filesystem', // 缓存类型,支持:memory、filesystem + cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'), // 缓存目录,仅类型为 filesystem 有效 + // 更多配置参考:https://webpack.docschina.org/configuration/other-options/#cache + }, +}; + +``` + +> 关于`cache`的更多配置参考:[https://webpack.docschina.org/configuration/other-options/#cache](https://webpack.docschina.org/configuration/other-options/#cache) + + +6. 资源模块 + +在`webpack4`中,针对资源型文件我们通常使用`file-loader`、`url-loader`、`raw-loader`进行处理 + +由于大部分前端项目都会用到资源型文件,因此`webpack5`原生支持了资源型模块 + +详见:[https://webpack.docschina.org/guides/asset-modules/](https://webpack.docschina.org/guides/asset-modules/) + +```javascript +// index.js +import bigPic from './assets/big-pic.png'; // 期望得到路径 +import smallPic from './assets/small-pic.jpg'; // 期望得到base64 +import yueyunpeng from './assets/yueyunpeng.gif'; // 期望根据文件大小决定是路径还是base64 +import raw from './assets/raw.txt'; // 期望得到原始文件内容 + +console.log('big-pic.png', bigPic); +console.log('small-pic.jpg', smallPic); +console.log('yueyunpeng.gif', yueyunpeng); +console.log('raw.txt', raw); + +``` + +```javascript +// webpack.config.js +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +module.exports = { + mode: 'development', + devtool: 'source-map', + entry: './src/index.js', + devServer: { + port: 8080, + }, + plugins: [new HtmlWebpackPlugin()], + output: { + filename: 'main.js', + path: path.resolve(__dirname, 'dist'), + assetModuleFilename: 'assets/[hash:5][ext]', // 在这里自定义资源文件保存的文件名 + }, + module: { + rules: [ + { + test: /\.png/, + type: 'asset/resource', // 作用类似于 file-loader + }, + { + test: /\.jpg/, + type: 'asset/inline', // 作用类似于 url-loader 文件大小不足的场景 + }, + { + test: /\.txt/, + type: 'asset/source', // 作用类似于 raw-loader + }, + { + test: /\.gif/, + type: 'asset', // 作用类似于 url-loader。在导出一个 data uri 和发送一个单独的文件之间自动选择 + generator: { + filename: 'gif/[hash:5][ext]', // 这里的配置会覆盖 assetModuleFilename + }, + parser: { + dataUrlCondition: { + maxSize: 4 * 1024, // 4kb以下使用 data uri + // 4kb以上就是文件 + }, + }, + }, + ], + }, +}; +``` +### 字节跳动面试题:说一下项目里有做过哪些 webpack 上的优化 +1. 对传输性能的优化 + - 压缩和混淆 使用 Uglifyjs 或其他类似工具对打包结果进行压缩、混淆,可以有效的减少包体积 + - tree shaking 项目中尽量使用 ESM,可以有效利用 tree shaking 优化,降低包体积 + - 抽离公共模块 将一些公共代码单独打包,这样可以充分利用浏览器缓存,其他代码变动后,不影响公共代码,浏览器可以直接从缓存中找到公共代码。 具体方式有多种,比如 dll、splitChunks + - 异步加载 对一些可以延迟执行的模块可以使用动态导入的方式异步加载它们,这样在打包结果中,它们会形成单独的包,同时,在页面一开始解析时并不需要加载它们,而是页面解析完成后,执行 JS 的过程中去加载它们。 这样可以显著提高页面的响应速度,在单页应用中尤其有用。 + - CDN 对一些知名的库使用 CDN,不仅可以节省打包时间,还可以显著提升库的加载速度 + - gzip 目前浏览器普遍支持 gzip 格式,因此可以将静态文件均使用 gzip 进行压缩 + - 环境适配 有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。 + +2. 对打包过程的优化 + - noParse 很多第三方库本身就是已经打包好的代码,对于这种代码无须再进行解析,可以使用 noParse 配置排除掉这些第三方库 + - externals 对于一些知名的第三方库可以使用 CDN,这部分库可以通过 externals 配置不进行打包 + - 限制 loader 的范围 在使用 loader 的时候,可以通过 exclude 排除掉一些不必要的编译,比如 babel-loader 对于那些已经完成打包的第三方库没有必要再降级一次,可以排除掉 + - 开启 loader 缓存 可以利用cache-loader缓存 loader 的编译结果,避免在源码没有变动时反复编译 + - 开启多线程编译 可以利用thread-loader开启多线程编译,提升编译效率 + - 动态链接库 对于某些需要打包的第三方库,可以使用 dll 的方式单独对其打包,然后 DLLPlugin 将其整合到当前项目中,这样就避免了在开发中频繁去打包这些库 + +3. 对开发体验的优化 + - lint 使用 eslint、stylelint 等工具保证团队代码风格一致 + - HMR 使用热替换避免页面刷新导致的状态丢失,提升开发体验 +## CSS +:::tip +CSS 渲染性能优化 +::: +1. 使用 id selector 非常的高效。在使用 id selector 的时候需要注意一点:因为 id 是唯一的,所以不需要既指定 id 又指定 tagName: +```css +/* Bad */ +p#id1 {color:red;} +/* Good */ +#id1 {color:red;} +``` +2. 不要使用 attribute selector,如:p[att1=”val1”]。这样的匹配非常慢。更不要这样写:p[id="id1"]。这样将 id selector 退化成 attribute selector。 +```css +/* Bad */ +p[id="jartto"]{color:red;} +p[class="blog"]{color:red;} +/* Good */ +#jartto{color:red;} +.blog{color:red;} +``` +3. 通常将浏览器前缀置于前面,将标准样式属性置于最后,类似: +```css +.foo { + -moz-border-radius: 5px; + border-radius: 5px; +} +``` +这里推荐参阅 CSS 规范-优化方案:http://nec.netease.com/standard/css-optimize.html +4. 遵守 CSSLint 规则 +font-faces 不能使用超过5个web字体 +import 禁止使用@import +regex-selectors 禁止使用属性选择器中的正则表达式选择器 +universal-selector 禁止使用通用选择器* +unqualified-attributes 禁止使用不规范的属性选择器 +zero-units 0后面不要加单位 +overqualified-elements 使用相邻选择器时,不要使用不必要的选择器 +shorthand 简写样式属性 +duplicate-background-images 相同的url在样式表中不超过一次 +更多的 CSSLint 规则可以参阅:https://github.com/CSSLint/csslint +5. 不要使用 @import +使用 @import 引入 CSS 会影响浏览器的并行下载。使用 @import 引用的 CSS 文件只有在引用它的那个 CSS 文件被下载、解析之后,浏览器才会知道还有另外一个 CSS 需要下载,这时才去下载,然后下载后开始解析、构建 Render Tree 等一系列操作。 +多个 @import 会导致下载顺序紊乱。在 IE 中,@import 会引发资源文件的下载顺序被打乱,即排列在 @import 后面的 JS 文件先于 @import 下载,并且打乱甚至破坏 @import 自身的并行下载。 +6. 避免过分重排(Reflow) +所谓重排就是浏览器重新计算布局位置与大小。常见的重排元素: +``` +width +height +padding +margin +display +border-width +border +top +position +font-size +float +text-align +overflow-y +font-weight +overflow +left +font-family +line-height +vertical-align +right +clear +white-space +bottom +min-height +``` +7. 依赖继承。如果某些属性可以继承,那么自然没有必要在写一遍。 +8. 其他:使用 id 选择器非常高效,因为 id 是唯一的;使用渐进增强的方案;值缩写;避免耗性能的属性;背景图优化合并;文件压缩 +## 网络层面 +### 总结 +- 优化打包体积:利用一些工具压缩、混淆最终打包代码,减少包体积 +- 多目标打包:利用一些打包插件,针对不同的浏览器打包出不同的兼容性版本,这样一来,每个版本中的兼容性代码就会大大减少,从而减少包体积 +- 压缩:现代浏览器普遍支持压缩格式,因此服务端的各种文件可以压缩后再响应给客户端,只要解压时间小于优化的传输时间,压缩就是可行的 +- CDN:利用 CDN 可以大幅缩减静态资源的访问时间,特别是对于公共库的访问,可以使用知名的 CDN 资源,这样可以实现跨越站点的缓存 +- 缓存:对于除 HTML 外的所有静态资源均可以开启协商缓存,利用构建工具打包产生的文件 hash 值来置换缓存 +- http2:开启 http2 后,利用其多路复用、头部压缩等特点,充分利用带宽传递大量的文件数据 +- 雪碧图:对于不使用 HTTP2 的场景,可以将多个图片合并为雪碧图,以达到减少文件的目的 +- defer、async:通过 defer 和 async 属性,可以让页面尽早加载 js 文件 +- prefetch、preload:通过 prefetch 属性,可以让页面在空闲时预先下载其他页面可能要用到的资源;通过 preload 属性,可以让页面预先下载本页面可能要用到的资源 +- 多个静态资源域:对于不使用 HTTP2 的场景,将相对独立的静态资源分到多个域中保存,可以让浏览器同时开启多个 TCP 连接,并行下载 (http2之前,浏览器开多个tcp,同一个域下最大数6个,为了多,静态资源分多个域存储,突破6个的限制) +### CDN +CDN(Content Delivery Network,内容分发网络)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。 +典型的CDN系统构成: +- 分发服务系统: 最基本的工作单元就是Cache设备,cache(边缘cache)负责直接响应最终用户的访问请求,把缓存在本地的内容快速地提供给用户。同时cache还负责与源站点进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保存在本地。Cache设备的数量、规模、总服务能力是衡量一个CDN系统服务能力的最基本的指标。 +- 负载均衡系统: 主要功能是负责对所有发起服务请求的用户进行访问调度,确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡(GSLB)和本地负载均衡(SLB)。全局负载均衡主要根据用户就近性原则,通过对每个服务节点进行“最优”判断,确定向用户提供服务的cache的物理位置。本地负载均衡主要负责节点内部的设备负载均衡 +- 运营管理系统: 运营管理系统分为运营管理和网络管理子系统,负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作,包含客户管理、产品管理、计费管理、统计分析等功能。 +#### 作用 +CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问。 +(1)在性能方面,引入CDN的作用在于: +- 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快 +- 部分资源请求分配给了CDN,减少了服务器的负载 +(2)在安全方面,CDN有助于防御DDoS、MITM等网络攻击: +- 针对DDoS:通过监控分析异常流量,限制其请求频率 +- 针对MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信 +除此之外,CDN作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。 +CDN还会把文件最小化或者压缩文档的优化 +#### 原理 +它的基本原理是:**架设多台服务器,这些服务器定期从源站拿取资源保存本地,到让不同地域的用户能够通过访问最近的服务器获得资源** +CDN和DNS有着密不可分的联系,先来看一下DNS的解析域名过程,在浏览器输入 www.test.com 的解析过程如下: +1. 检查浏览器缓存 +2. 检查操作系统缓存,常见的如hosts文件 +3. 检查路由器缓存 +4. 如果前几步都没没找到,会向ISP(网络服务提供商)的LDNS服务器查询 +5. 如果LDNS服务器没找到,会向根域名服务器(Root Server)请求解析,分为以下几步: + +- 根服务器返回顶级域名(TLD)服务器如.com,.cn,.org等的地址,该例子中会返回.com的地址 +- 接着向顶级域名服务器发送请求,然后会返回次级域名(SLD)服务器的地址,本例子会返回.test的地址 +- 接着向次级域名服务器发送请求,然后会返回通过域名查询到的目标IP,本例子会返回www.test.com的地址 +- Local DNS Server会缓存结果,并返回给用户,缓存在系统中 +CDN的工作原理: +1. 用户未使用CDN缓存资源的过程: +2. 浏览器通过DNS对域名进行解析(就是上面的DNS解析过程),依次得到此域名对应的IP地址 +3. 浏览器根据得到的IP地址,向域名的服务主机发送数据请求 +4. 服务器向浏览器返回响应数据 + +(2)用户使用CDN缓存资源的过程: + +1. 对于点击的数据的URL,经过本地DNS系统的解析,发现该URL对应的是一个CDN专用的DNS服务器,DNS系统就会将域名解析权交给CNAME指向的CDN专用的DNS服务器。 +2. CND专用DNS服务器将CND的全局负载均衡设备IP地址返回给用户 +3. 用户向CDN的全局负载均衡设备发起数据请求 +4. CDN的全局负载均衡设备根据用户的IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求 +5. 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的IP地址返回给全局负载均衡设备 +6. 全局负载均衡设备把服务器的IP地址返回给用户 +7. 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。 + +如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器请求内容,以此类推,直到获取到需要的资源。最后如果还是没有,就会回到自己的服务器去获取资源。 + +CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的IP地址,或者该域名的一个CNAME,然后再根据这个CNAME来查找对应的IP地址。 +#### 使用场景 +- 使用第三方的CDN服务:如果想要开源一些项目,可以使用第三方的CDN服务 +- 使用CDN进行静态资源的缓存:将自己网站的静态资源放在CDN上,比如js、css、图片等。可以将整个项目放在CDN上,完成一键部署。 +- 直播传送:直播本质上是使用流媒体进行传送,CDN也是支持流媒体传送的,所以直播完全可以使用CDN来提高访问速度。CDN在处理流媒体的时候与处理普通静态文件有所不同,普通文件如果在边缘节点没有找到的话,就会去上一层接着寻找,但是流媒体本身数据量就非常大,如果使用回源的方式,必然会带来性能问题,所以流媒体一般采用的都是主动推送的方式来进行。 +TODO:cdn加速原理,没有缓存到哪里拿,CDN回源策略 +**分发的内容** + +静态内容:即使是静态内容也不是一直保存在cdn,源服务器发送文件给CDN的时候就可以利用HTTP头部的cache-control可以设置文件的缓存形式,cdn就知道哪些内容可以保存no-cache,那些不能no-store,那些保存多久max-age +动态内容: + +工作流程: + +静态内容:源服务器把静态内容提前备份给cdn(push),世界各地访问的时候就进的cdn服务器会把静态内容提供给用户,不需要每次劳烦源服务器。如果没有提前备份,cdn问源服务器要(pull),然后cdn备份,其他请球的用户可以马上拿到。 + +动态内容:源服务器很难做到提前预测到每个用户的动态内容提前给到cdn,如果等到用户索取动态内容cdn再向源服务器获取,这样cdn提供不了加速服务。但是有些是可以提供动态服务的:时间,有些cdn会提供可以运行在cdn上的接口,让源服务器用这些cdn接口,而不是源服务器自己的代码,用户就可以直接从cdn获取时间 + +问:cdn用什么方式来转移流量实现负载均衡? + +和DNS域名解析根服务器的做法相似:任播通信:服务器对外都拥有同一个ip地址,如果收到了请求,请求就会由距离用户最近的服务器响应,任播技术把流量转移给没超载的服务器可以缓解。CDN还会用TLS/SSL证书对网站进行保护。 +我们可以把项目中的所有静态资源都放到CDN上(收费),也可以利用现成免费的CDN获取公共库的资源 +![](../public/front-end-engineering/2024-01-28-17-18-12.png) +```js + +首先,我们需要告诉webpack不要对公共库进行打包 +// vue.config.js +module.exports = { + configureWebpack: { + externals: { + vue: "Vue", + vuex: "Vuex", + "vue-router": "VueRouter", + } + }, +}; +然后,在页面中手动加入cdn链接,这里使用bootcn +对于vuex和vue-router,使用这种传统的方式引入的话会自动成为Vue的插件,因此需要去掉Vue.use(xxx) + +我们可以使用下面的代码来进行兼容 +// store.js +import Vue from "vue"; +import Vuex from "vuex"; +if(!window.Vuex){ + // 没有使用传统的方式引入Vuex + Vue.use(Vuex); +} +// router.js +import VueRouter from "vue-router"; +import Vue from "vue"; +if(!window.VueRouter){ + // 没有使用传统的方式引入VueRouter + Vue.use(VueRouter); +} +``` +### 增加带宽 +增加带宽可以提高资源的访问速度,从而提高首批的加载速度,我司项目带宽由 2M 升级到 5M,效果明显。 +### http内置优化 +- Http2 + - 头部压缩:专门的 HPACK 压缩算法 + - 索引表:客户端和服务器共同维护的一张表,表的内容分为 61 位的静态表(保存常用信息,例如:host/content-type)和动态表 + - 霍夫曼编码 +- 链路复用 + - Http1 建立起 Tcp 连接,发送请求之后,服务器在处理请求的等待期间,这个期间又没有数据去发送,称为空挡期。链接断开是在服务器响应回溯之后 + - keep-alive 链接保持一段时间 + - HTTP2 可以利用空档期 + - 不需要再重复建立链接 +- 二进制帧 + - Http1.1 文本字符分割的数据流,解析慢且容易出错 + - 二进制帧:帧长度、帧类型、帧标识 +补充:采用 Http2 之后,可以减少资源合并的操作,因为首部压缩已经减少了多请求传输的数据量 + +### 数据传输层面 +- 缓存:浏览器缓存 + - 强缓存 + - cache-contorl: max-age=30 + - expires: Wed, 21 Oct 2021 07:28:00 GMT +- 协商缓存 + - etag + - last-modified + - if-modified-since + - if-none-match +- 压缩 + - 数据压缩:gzip + - 代码文件压缩:HTML/CSS/JS 中的注释、空格、长变量等 + - 静态资源:字体图标,去除元数据,缩小尺寸以及分辨率 + - 头与报文 + - http1.1 中减少不必要的头 + - 减少 cookie 数据量 ## Vue -### **如何实现 _vue_ 项目中的性能优化?** +### Vue 开发优化 + +#### 使用key + +对于通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除、新增、改动元素 + +#### 使用冻结的对象 + +冻结的对象不会被响应化 + +#### 使用函数式组件 + +参见[函数式组件](https://v2.cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6) + +#### 使用计算属性 + +如果模板中某个数据会使用多次,并且该数据是通过计算得到的,使用计算属性以缓存它们 + +#### 非实时绑定的表单项 + +当使用v-model绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue发生重渲染(rerender),这会带来一些性能的开销。 + +特别是当用户改变表单项时,页面有一些动画正在进行中,由于JS执行线程和浏览器渲染线程是互斥的,最终会导致动画出现卡顿。 + +我们可以通过使用lazy或不使用v-model的方式解决该问题,但要注意,这样可能会导致在某一个时间段内数据和表单项的值是不一致的。 + + +#### 保持对象引用稳定 + +在绝大部分情况下,vue触发rerender的时机是其依赖的数据发生变化 + +若数据没有发生变化,哪怕给数据重新赋值了,vue也是不会做出任何处理的 + +下面是vue判断数据没有变化的源码 + +```js +// value 为旧值, newVal 为新值 +if (newVal === value || (newVal !== newVal && value !== value)) {//NaN + return +} +``` + +因此,如果需要,只要能保证组件的依赖数据不发生变化,组件就不会重新渲染。 + +对于原始数据类型,保持其值不变即可 + +对于对象类型,保持其引用不变即可 + +从另一方面来说,由于可以通过保持属性引用稳定来避免子组件的重渲染,那么我们应该细分组件来尽量避免多余的渲染 + +#### 使用v-show替代v-if + +对于频繁切换显示状态的元素,使用v-show可以保证虚拟dom树的稳定,避免频繁的新增和删除元素,特别是对于那些内部包含大量dom元素的节点,这一点极其重要 + +关键字:频繁切换显示状态、内部包含大量dom元素 + +#### 使用延迟装载(defer) + +首页白屏时间主要受到两个因素的影响: + +- 打包体积过大 巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个`