阅读 56

杂谈:前端性能优化的思路

高频面试题:在以往开发的项目中做过哪些优化?

为什么要做优化,大抵会有以下一些原因:

  • 白屏时间过长

  • 页面卡顿

这些问题都会让客户浏览体验很差,轻则关闭,重则将永远失去这个用户,这就是为什么要做网站性能优化的原因

那么,我们该从哪些方面入手呢?

其实,从浏览器地址栏输入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文件

image.png

如果在上线打包前忘记关掉source map,那么,打包后的文件中就会产生.map文件。

如果上线前我们注释了devtool: 'source-map',那么,打包后的文件中就没有了.map文件。

结论:关闭source map不会产生.map文件,进而减小打包后的文件体积。

25、管理输出

假设我们的webpack的配置有两个入口print.jsindex.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'),     }, }; 复制代码

此时打包后的文件会有:

image.png

此时,我们关闭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时,打包后的结果为:

image.png

结论:打包输出项中配置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', }, 复制代码

此时执行的结果为:

image.png

(2)防止重复

虽然以上方式能够完成代码分割的目的,但是,会存在以下隐患:

  • 如果每个文件中都有重复的文件,那些重复模块都会被引入到各个bundle中。

  • 以上方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

这种问题可以通过以下两种方式解决:

①配置dependOn
entry: {     index: {         import: './src/index.js',         dependOn: 'shared',     },     another: {         import: './src/another-module.js',         dependOn: 'shared',     },     shared: 'lodash', }, 复制代码

此时,打包的文件中就会多一个公共的文件,作为公共模块lodash.js文件:

image.png

SplitChunksPlugin

配置optimization.splitChunks选项:

entry: {     index: './src/index.js',     another: './src/another-module.js', }, optimization: {     splitChunks: {         chunks: 'all',     }, }, 复制代码

执行结果如下:

image.png

此时,打包的文件中就会多一个公共的文件,作为公共模块venders类的文件:

(3)动态导入

  • import语法:通过动态导入的方式,也可以实现代码的分割。

27、缓存

当打包的文件放入到服务器后,如果客户端每次访问都从服务端拉取资源的话,也许每次都拉取的是没有更新过的资源。如果,每次拉取的都是更新过的资源,而未更新的资源使用缓存,那么就会提高二次访问时的效率,用户直观感受可能是第二次白屏时间更短。下面介绍其可实现的思路:

(1)输出文件的文件名

通过配置output.filename的方式为其增加.[contenthash],只有当资源内容发生改变时才会让打包后的文件名发生改变:

output: {     filename: '[name].[contenthash].js',     path: path.resolve(__dirname, 'dist'), }, 复制代码

首次加载资源时:

image.png

再次加载资源时:

image.png

通过对比可以发现,第二次加载时资源加载状态为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',             },         },     }, }, 复制代码

结果为:

image.png

因为例如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文件进行擦除。

总结

优化内容有些多,大体可以就拉取资源体积的大小,请求速度和资源组织方式角度去优化。而且,也可以通过浏览器开发者工具中的PerformanceLighthouse模块生成加载或者性能报告,根据其提示的思路去优化页面。

写在最后

性能优化的内容不仅这些,以上内容仅用来做个引子,希望给看到的学友提供些思路,纰漏之处在所难免,请批评指正。


作者:qb
链接:https://juejin.cn/post/7169131511390666789


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐