性能优化(网络方向)
web 应用无非是两台主机之间互相传输数据包的一个过程; 如何减少传输过程的耗时就是网络方向优化的重点, 优化出发点从第一篇文章中说起
DNS 解析过程的优化
当浏览器从第三方服务跨域请求资源的时候, 在浏览器发起请求之前, 这个第三方的跨域域名需要被解析为一个 IP 地址, 这个过程就是 DNS 解析;
DNS 缓存可以用来减少这个过程的耗时, DNS 解析可能会增加请求的延迟, 对于那些需要请求许多第三方的资源的网站而言, DNS 解析的耗时延迟可能会大大降低网页加载性能.
dns-prefetch
当站点引用跨域域上的资源时, 都应在 < head > 元素中放置 dns-prefetch 提示, 但是要记住一些注意事项. 首先, dns-prefetch 仅对跨域域上的 DNS 查找有效, 因此请避免将其用于您当前访问的站点
- <link rel="dns-prefetch" href="https://fonts.googleapis.com/">
- preconnect
由于 dns-prefetch 仅执行 DNS 查找, 但 preconnect 会建立与服务器的连接. 如果站点是通过 HTTPS 服务的, 则此过程包括 DNS 解析, 建立 TCP 连接以及执行 TLS 握手. 将两者结合起来可提供机会, 进一步减少跨源请求的感知延迟
- <!-- 注意顺序, precontent 和 dns-prefetch 的兼容性 -->
- <link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin>
- <link rel="dns-prefetch" href="https://fonts.googleapis.com/">
TCP 传输阶段优化
这个前端方面好像能做的有限, 我们都知道 http 协议 是基于 tcp 的;
升级 http 协议版本可以考虑下, 比如把 http/1.0 -> http/1.1 -> http/2;
这个需要我们在应用服务器上配置(nginx, Apache 等), 不做概述了, 另外还需要客户端和服务器都支持哦, 目前还没开发出稳定版本, 好多只支持 https, 不过也不远了...
http2 的优势
# 1. 多路复用: 同一个 tcp 连接传输多个资源
这样可以突破统一域名下只允许有限个 tcp 同时连接,
这样 http1.1 所做的减少请求数优化就没有太大必要了
如多张小图合成一张大图(雪碧图), 合并 JS 和 CSS 文件
# 2. 报文头压缩和二进制编码: 减少传输体积
http1 中第一次请求有完整的 http 报文头部, 第二次请求的也是;
http2 中第一次请求有完整的 http 报文头部, 第二次请求只会携带 path 字段;
这样就大大减少了发送的量. 这个的实现要求客户端和服务同时维护一个报文头表.
# 3.Server Push
http2 可以让服务先把其它很可能客户端会请求的资源 (比如图片) 先 push 发给你,
不用等到请求的时候再发送, 这样可以提高页面整体的加载速度
但目前支持性不太好...emm...
总的来说, 在 c 端业务下不会太普及, 毕竟需要软件支持才行...
http 请求响应阶段优化
为了让数据包传输的更快, 我们可以从两个方面入手: 请求的数据包大小(服务器), 请求数据包的频率(客户端)
减少请求文件的大小
请求文件对应的是我们项目完成后, 打包所指的静态资源文件(会被部署到服务器), 文件越小, 传输的数据包也会相对较小, 讲道理也会更快到达客户端
how to reduce a package size?
目前我们都会使用打包工具了(比如 webpack, rollup, glup 等), 如何使用工具来减小包的体积呢? 这边建议您去官网文档 https://www.webpackjs.com/guides/installation/ 呢... 当然这里列举一下常用的手段(webpack 的), 但是注意要插件版本更新哦
JS 文件压缩
- const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
- module.exports = {
- plugins: [
- new UglifyJsPlugin({
- // 允许并发
- parallel: true,
- // 开启缓存
- cache: true,
- compress: {
- // 删除所有的 console 语句
- drop_console: true,
- // 把使用多次的静态值自动定义为变量
- reduce_vars: true,
- },
- output: {
- // 不保留注释
- comment: false,
- // 使输出的代码尽可能紧凑
- beautify: false
- }
- })
- ]
- }
CSS 文件压缩
- // optimize-CSS-assets-webpack-plugin
- plugins: [
- new OptimizeCSSAssetsPlugin({
- assetNameRegExp: /\.CSS$/g,
- cssProcessor: require('cssnano'),
- }),
- ];
html 文件压缩
- // HTML-webpack-plugin
- plugins: [
- new HtmlWebpackPlugin({
- template: path.join(__dirname, 'src/index.html'),
- filename: 'index.html',
- chunks: ['index'],
- inject: true,
- minify: {
- html5: true,
- collapseWhitespace: true,
- preserveLineBreaks: false,
- minifyCSS: true,
- minifyJS: true,
- removeComments: false,
- },
- }),
- ];
source map 文件关闭
tree shaking
1. 代码不会被执行, 不可到达, 比如 if(false){// 这里边的代码}
2. 代码执行的结果不会被用到
3. 代码只会影响死变量(只写不读)
4. 方法不能有副作用
// 原理相关: 以后在研究
利用 ES6 模块的特点:
只能作为模块顶层的语句出现
import 的模块名只能是字符串常量
import binding 是 immutable 的
代码擦除: uglify 阶段删除无用代码
scope hoisting(作用域提升)
分析出模块之间的依赖关系, 尽可能的把打散的模块合并到一个函数中去, 但前提是不能造成代码冗余
- const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
- module.exports = {
- resolve: {
- // 针对 NPM 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
- mainFields: ['jsnext:main', 'browser', 'main']
- },
- plugins: [
- // 开启 Scope Hoisting
- new ModuleConcatenationPlugin(),
- ],
- };
项目中使用按需加载, 懒加载(路由, 组件级)
- const router = new vueRouter({
- routes: [
- { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ './Foo.vue') }
- { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './Bar.vue') }
- ]
- })
开启 gizp 压缩
有时候启用也会消耗服务器性能, 看情况使用吧
暂时先提这么些吧... 后续想到了再加
减少请求频率
因为同一域名下 tcp 连接数的限制导致过多的请求会排队阻塞, 所以我们需要尽量控制请求的数量和频率
常见措施
将静态资源的内联到 HTML 中
这样这些资源无需从服务器获取, 但可能影响到渲染进程...
- <!-- 1. 小图片内联 base64 (url-loader) -->
- <!-- 2.css 内联 -->
- <!-- 3.js 内联 -->
- <script>
- ${require('raw-loader!babel-loader!./node_modules/lib-flexible/flexible.js')}
- </script>
利用各级缓存(下一篇存储方面介绍)
通常都是在服务端做相关配置, 但你要知道
我们可以利用 http 缓存 (浏览器端) 来减少和拦截二次请求, 当然一般都是在服务端设置的;
服务器端也可以设置缓存(Redis 等), 减少数据查询的时间同样可以缩短整个请求时间
利用本地存储
我们可以将常用不变的信息存在本地(cookie,storage API 等);
判断存在就不去请求相关的接口, 或者定期去请求也是可以的
花钱买 CDN 加速
CDN 又叫内容分发网络, 通过把资源部署到世界各地, 用户在访问时按照就近原则从离用户最近的服务器获取资源, 从而加速资源的获取速度. CDN 其实是通过优化物理链路层传输过程中的网速有限, 丢包等问题来提升网速的...
购买 cdn 服务器;
然后把网页的静态资源上传到 CDN 服务上去,
在请求这些静态资源的时候需要通过 CDN 服务提供的 URL 地址去访问;
# 注意, cdn 缓存导致的新版本发布后不生效的问题
所以打包的时候常在文件后面加上 hash 值
然后在 HTML 文件中的资源引入地址也需要换成 CDN 服务提供的地址
- /alicdn/xx12dsa311.JS
- # 利用不同域名的 cdn 去存放资源, (tcp 连接限制)
webpack 构建时添加 cdn
- // 静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL.
- // 静态资源的文件名称需要带上有文件内容算出来的 Hash 值, 以防止被缓存.
- // 不同类型的资源放到不同域名的 CDN 服务上去, 以防止资源的并行加载被阻塞.
- module.exports = {
- // 省略 entry 配置...
- output: {
- // 给输出的 JavaScript 文件名称加上 Hash 值
- filename: '[name]_[chunkhash:8].js',
- path: path.resolve(__dirname, './dist'),
- // 指定存放 JavaScript 文件的 CDN 目录 URL
- publicPath: '//js.cdn.com/id/',
- },
- module: {
- rules: [
- {
- // 增加对 CSS 文件的支持
- test: /\.CSS$/,
- // 提取出 Chunk 中的 CSS 代码到单独的文件中
- use: ExtractTextPlugin.extract({
- // 压缩 CSS 代码
- use: ['css-loader?minimize'],
- // 指定存放 CSS 中导入的资源 (例如图片) 的 CDN 目录 URL
- publicPath: '//img.cdn.com/id/'
- }),
- },
- {
- // 增加对 PNG 文件的支持
- test: /\.PNG$/,
- // 给输出的 PNG 文件名称加上 Hash 值
- use: ['file-loader?name=[name]_[hash:8].[ext]'],
- },
- // 省略其它 Loader 配置...
- ]
- },
- plugins: [
- // 使用 WebPlugin 自动生成 HTML
- new WebPlugin({
- // HTML 模版文件所在的文件路径
- template: './template.html',
- // 输出的 HTML 的文件名称
- filename: 'index.html',
- // 指定存放 CSS 文件的 CDN 目录 URL
- stylePublicPath: '//css.cdn.com/id/',
- }),
- new ExtractTextPlugin({
- // 给输出的 CSS 文件名称加上 Hash 值
- filename: `[name]_[contenthash:8].css`,
- }),
- // 省略代码压缩插件配置...
- ],
- };
- /*
- 以上代码中最核心的部分是通过 publicPath 参数设置存放静态资源的 CDN 目录 URL,
- 为了让不同类型的资源输出到不同的 CDN, 需要分别在:
- output.publicPath 中设置 JavaScript 的地址.
- CSS-loader.publicPath 中设置被 CSS 导入的资源的的地址.
- WebPlugin.stylePublicPath 中设置 CSS 文件的地址.
- 设置好 publicPath 后, WebPlugin 在生成 HTML 文件和 CSS-loader 转换 CSS 代码时, 会考虑到配置中的 publicPath, 用对应的线上地址替换原来的相对地址.
- */
参考
DNS MDN]
webpack 文档 https://www.webpackjs.com/guides/
深入浅出 Webpack https://webpack.wuhaolin.cn/
Scope Hoisting
来源: https://segmentfault.com/a/1190000022522349