杂谈:前端性能优化的思路
高频面试题:在以往开发的项目中做过哪些优化?
为什么要做优化,大抵会有以下一些原因:
白屏时间过长
页面卡顿
这些问题都会让客户浏览体验很差,轻则关闭,重则将永远失去这个用户,这就是为什么要做网站性能优化的原因
。
那么,我们该从哪些方面入手呢?
其实,从浏览器地址栏输入url到页面展示的过程中,主要就是拉取资源体积的大小,请求速度和资源组织方式这三个方面的问题,可以从这入手。
一、http请求
1、采用域名分片技术
如果我们不得不使用http1协议时,可以通过域名分片技术,将资源放到多个域名下,因为一个域名最多可以开启6个TCP连接,这样资源请求的过程中多个域名就会开启多个TCP连接,一定程度上可以让请求速度变快。
2、http1升级成http2
因为http2通过引入二进制分帧层,就实现了 HTTP 的多路复用技术,该技术可以设置请求的优先级、进行主要资源的服务器推送和头部压缩,所以http1升级成http2可以让请求速度更快。
3、开启keep-alive
一般服务端向客户端返回了数据以后,就会关闭TCP,客户端再次发出请求时,会再次启动TCP,我们知道TCP的启动是慢启动,所以我们通过keep-alive
让TCP保持打开状态,提升资源加载速度。
4、开启gzip压缩
可以通过开启gzip
压缩,让客户端拉取到的数据是经过压缩的,减小文件体积,进而提升资源获取速度。
5、减少无用的请求头数据
客户端在向服务端获取数据时,会携带请求数据,有些无用的token、cookie信息或者请求体中的数据都可以去除。
二、基础优化
6、非重要文件采用异步加载方式
在.html文件中,有些资源是渲染页面必须的,有些资源可以迟一些再加载,为了减小阻塞,可以通过defer
或者async
的方式让非重要文件采用异步加载的方式,优化资源组织方式。
7、删除辅助开发的console信息
在平时开发的时候,会通过console.log等方式去打印信息,在上生产环境时,需要删除这些信息,以减小文件体积。
8、script文件放置位置
因为javascript是单线程语言,计算量比较大或者加载比较耗时的script资源会阻塞文件的DOM渲染,为了让页面先进行展示,我们可以将script文件放置到body结束标签之前。
9、css样式采用媒体查询
需要根据不同场景进行样式展示时,尽量避免通过javascript的方式去判断,并加载css资源,而应该采取媒体查询的方式,以减小文件体积。
10、服务端渲染优化
如果是服务端的SSR项目,可以从减小资源体积方面考虑服务端渲染有没有可优化的点。
11、注意代码规范
抽取公共组件,公共js,公共css样式,减小代码体积。删除无用代码,减少非必要注释。防止写出死循环等等
12、字体图标的使用
有些图片图标尽可能使用字体图标,图片的体积一般都会比字体图标大
三、框架特性(vue)
13、v-if和v-show
频繁切换时使用v-show
,利用其缓存特性,减小切换开销;需要考虑首屏渲染的场景时使用v-if
,如果为false
则不进行渲染,可以减小节点数量。
14、v-for的key
列表变化时,循环时使用唯一不变的key
,借助其本地复用策略,减小创建新节点的开销,当然列表只进行一次渲染时,key
采用循环的index
也可以。
15、侦听器和计算属性
侦听器watch
用于数据变化时引起其他行为;compouter
计算属性顾名思义就是新计算而来的属性,如果依赖的数据未发生变化,不会触发重新计算,所以在依赖数据计算获取新数据的场景可以选择compouter
。
16、合理使用生命周期
在destroyed
阶段进行绑定事件或者定时器的销毁;使用动态组件的时候通过keep-alive
包裹进行缓存处理,相关的操作可以在actived
阶段激活。
17、数据响应式处理
不需要响应式处理的数据可以通过Object.freeze
处理,或者直接通过this.xxx = xxx
的方式进行定义;需要响应式处理的属性可以通过this.$set
的方式处理,而不是JSON.parse(JSON.stringify(XXX))
的方式。
18、路由加载方式
页面路由组件可以采用懒加载的方式,只有请求到当前路由资源时,才去服务器拉取数据。
19、异步组件的使用
页面渲染过程中可以先让重要的页面结构进行渲染,非重要页面采取异步组件的方式。
20、插件引入方式
第三方插件可以采用按需加载的方式,比如element-ui
。
21、减少代码量
- 采用
mixin
的方式抽离公共方法 - 抽离公共组件
- 定义公共方法至公共
js
中 - 抽离公共
css
22、编译方式
如果线上需要template
的编译,可以采用完成版vue.esm.js
;如果线上无需template
的编译,可采用运行时版本vue.runtime.esm.js
,相比完整版体积要小大约30%
23、渲染方式
一些企业内部使用的后端管理系统可以采用前端渲染的方式;如果是需要考虑首屏加载或者SEO
的网站可以采用服务端渲染的方式。
四、打包工具(webpack)
24、环境切换
在开发环境中我们打开source map
的目的是为了追踪错误的具体文件和文件中的具体位置,devtool
的配置是:
module.exports = {
mode: 'production',
devtool: 'source-map',
// 省略其他配置
};
那么打包的文件中就会有.map
文件
如果在上线打包前忘记关掉source map
,那么,打包后的文件中就会产生.map
文件。
如果上线前我们注释了devtool: 'source-map'
,那么,打包后的文件中就没有了.map
文件。
结论:关闭source map
不会产生.map
文件,进而减小打包后的文件体积。
25、管理输出
假设我们的webpack的配置有两个入口print.js
和index.js
,同时,也开启了source map
:
module.exports = {
mode: 'development',
devtool: 'source-map', // 产生.map文件
entry: {
index: './src/index.js',
print: './src/print.js', // 产生print.js的打包文件
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
此时打包后的文件会有:
此时,我们关闭source map
,并且,删掉了print.js
文件,配置如下:
module.exports = {
mode: 'development',
// devtool: 'source-map', // 注释了,就没有.map文件了
entry: {
index: './src/index.js',
// print: './src/print.js', // 注释了,就应该没有print.js的打包文件啦
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
但是,打包后的文件夹下,之前的文件依然存在。
假设,上线前忘记打开文件自动清除,那么,线上包的体积会比较大。
相信不会忘的,当我们在输出项output
中配置clean:true
时,打包后的结果为:
结论:打包输出项中配置clean:true
可以只产生需要的文件,清除遗留的无用的文件,达到减小打包文件的体积。
26、代码分割
我们知道客户端拉取服务端资源的时候,如果包的体积很大,会影响加载速度,webpack可以通过代码分割的方式来生成多个小文件,可以通过控制各个资源加载的优先级,提高资源加载速度。代码分割实现方式主要有以下几种:
假如我们有另外一个文件anthoer-module.js。
// anthoer-module.js文件
import _ from 'lodash';
console.log(_.join(['Another', 'module', 'loaded!'], ' '));
(1)入口起点
我们配置入口文件:
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
此时执行的结果为:
(2)防止重复
虽然以上方式能够完成代码分割的目的,但是,会存在以下隐患:
- 如果每个文件中都有重复的文件,那些重复模块都会被引入到各个bundle中。
- 以上方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
这种问题可以通过以下两种方式解决:
①配置dependOn
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
},
此时,打包的文件中就会多一个公共的文件,作为公共模块lodash.js
文件:
②SplitChunksPlugin
配置optimization.splitChunks
选项:
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
执行结果如下:
此时,打包的文件中就会多一个公共的文件,作为公共模块venders
类的文件:
(3)动态导入
- import语法:通过动态导入的方式,也可以实现代码的分割。
27、缓存
当打包的文件放入到服务器后,如果客户端每次访问都从服务端拉取资源的话,也许每次都拉取的是没有更新过的资源。如果,每次拉取的都是更新过的资源,而未更新的资源使用缓存,那么就会提高二次访问时的效率,用户直观感受可能是第二次白屏时间更短。下面介绍其可实现的思路:
(1)输出文件的文件名
通过配置output.filename
的方式为其增加.[contenthash]
,只有当资源内容发生改变时才会让打包后的文件名发生改变:
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
首次加载资源时:
再次加载资源时:
通过对比可以发现,第二次加载时资源加载状态为304,利用了缓存,减小了资源数据的拉取。
(2)提取引导模板
通过runtimeChunk: 'single'
的方式为所有打包后的bundle创建一个runtime bundle,并且通过配置optimization.splitChunks.cacheGroups
的方式将公共资源抽取到vendor chunk文件中。
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
结果为:
因为例如lodash和vue这样的文件体积比较大且基本不发生变化的文件,拆成单独的vendor chunk文件,客户端可以使用其缓存。
28、tree-shaking
主要针对import
或者export
静态引入的语法,通过mode:'production'
选项的配置可以自动开启tree-shaking,指的是未使用到的模块不会打包到bundle.js中去。
先创建一个公共文件math.js:
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
然后在index.js文件中使用cube方法,但是不使用square方法:
import { cube } from './math.js';
function component() {
const element = document.createElement('pre');
element.innerHTML = cube(5);
return element;
}
document.body.appendChild(component());
以上打包在mode:'development'
模式下,会将square方法也打包进去。如果在mode:'production'
模式下,不会将square方法打包进去。
结论:在线上运行时,通过开启mode:'production'
的方式开启tree-shaking,可以减少资源包的体积。
29、预加载/预获取
Webpack v4.6.0+ 增加了对预获取和预加载的支持。 在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
30、懒加载/按需加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
一般是在某些触发行为发生后,再通过import
的方式引入,引入的过程中再去服务端拉取资源。
31、shim/pollyfill
通过shim,可以仅仅将其中需要的方法作为全局变量,达到按需引入的目的。
32、压缩图片/压缩文件
在项目使用过程中,有时候一张图片有好几M
,这无疑会降低页面渲染速度。可以在不降低用户体验的情况下,根据webpack中图片压缩的相关配置,进行压缩,以减小图片体积。
33、CSS的擦除
通过purgecss-webpack-plugin
的方式对无用的css文件进行擦除。
总结
优化内容有些多,大体可以就拉取资源体积的大小,请求速度和资源组织方式角度去优化。而且,也可以通过浏览器开发者工具中的
Performance
和Lighthouse
模块生成加载或者性能报告,根据其提示的思路去优化页面。
写在最后
性能优化的内容不仅这些,以上内容仅用来做个引子,希望给看到的学友提供些思路,纰漏之处在所难免,请批评指正。
如果本文有些帮助,请点赞+收藏。
转载自:https://juejin.cn/post/7169131511390666789