前端是庞大的,包括 HTML、 CSS、 Javascript、Image 、Flash等等各种各样的资源。前端优化是复杂的,针对方方面面的资源都有不同的方式。那么,前端优化的目的是什么 ?
1. 从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
2. 从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。
总之,恰当的优化不仅能够改善站点的用户体验并且能够节省相当的资源利用。
前端优化的途径有很多,按粒度大致可以分为两类,第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;第二类则是代码级别的优化,例如 Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。另外,本着提高投入产出比的目的,后文提到的各种优化策略大致按照投入产出比从大到小的顺序排列。
一、浏览器
1、减少 HTTP 请求
1、使用字体图标 iconfont 代替图片图标
字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便。并且字体图标是矢量图,不会失真。还有一个优点是生成的文件特别小。
2、图片sprite
南方叫精灵图,北方叫雪碧图。这个现象就很有趣。
在网站中有很多小图片的时候,一定要把这些小图片合并为一张大的图片,然后通过background分割到需要展示的图片。
3、白屏loading
2、缓存
1、Session
2、localStorage(需要注意安全问题,防篡改)
3、减少cookie传输
4、避免过多的回流与重构
什么操作会导致重排?
●添加或删除可见的 DOM 元素
●元素位置改变
●元素尺寸改变
●内容改变
●浏览器窗口尺寸改变
如何减少重排重绘?
用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。
5、将 CSS 放在文件头部,JavaScript 文件放在底部
所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染(CSS 不会阻塞 DOM 解析)。如果这些 CSS 和 JS 需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件。
那为什么 CSS 文件还要放在头部呢?
因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。
另外,JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。
二、服务端
1、Gzip
服务端配置gzip压缩后可大大缩减资源大小。
2、页面静态化
3、http缓存
1、Keep-alive
判断是否开启:看response headers中有没有Connection: keep-alive 。开启以后,看network的瀑布流中就没有 Initial connection耗时了
ginx设置keep-alive(默认开启)
# 0 为关闭 #keepalive_timeout 0;
# 65s无连接 关闭 keepalive_timeout 65;
# 连接数,达到100断开 keepalive_requests 100;
2、Cache-Control / Expires / Max-Age
设置资源是否缓存,以及缓存时间
3、Etag / If-None-Match
资源唯一标识作对比,如果有变化,从服务器拉取资源。如果没变化则取缓存资源,状态码304,也就是协商缓存
4、ast-Modified / If-Modified-Since
通过对比时间的差异来觉得要不要从服务器获取资源
4、静态资源使用 CDN
当用户访问一个网站时,如果没有 CDN,过程是这样的:
1、浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
2、本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到网站服务器的 IP 地址。
3、本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。
添加cdn后
本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
浏览器再根据 SLB 发回的地址重定向到缓存服务器。
三、压缩优化
1、css压缩(MiniCssExtractPlugin)
2、js压缩(UglifyPlugin)
3、图片压缩
1、图片延迟加载
2、Base64小图片减少请求
3、降低图片质量
4、尽可能利用 CSS3 效果代替图片
5、使用 webp 格式的图片
WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。
4、字体压缩
使用 fontmin-webpack 插件对字体文件进行压缩
5、压缩工具webpack
1、tree shaking
中文(摇树),webpack构建优化中重要一环。摇树用于清除我们项目中的一些无用代码,它依赖于ES中的模块语法。
import _ from 'lodash' 和 import _isEmpty from 'lodash/isEmpty'; 区别一个全部提取,一个局部提取
2、split chunks分包
入口文件依赖的文件都被打包进了main.js,那些大于 30kb 的第三方包,如:echarts、xlsx、dropzone等都被单独打包成了一个个独立 bundle。
其它被我们设置了异步加载的页面或者组件变成了一个个chunk,也就是被打包成独立的bundle。它内置的代码分割策略是这样的:
新的 chunk 是否被共享或者是来自 node_modules 的模块
新的 chunk 体积在压缩之前是否大于 30kb
按需加载 chunk 的并发请求数量小于等于 5 个
页面初始加载时的并发请求数量小于等于 3 个
3、exclude/include
我们可以通过 exclude、include 配置来确保转译尽可能少的文件。顾名思义,exclude 指定要排除的文件,include 指定要包含的文件。
exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。
4、DllPlugin
有些时候,如果所有的JS文件都打成一个JS文件,会导致最终生成的JS文件很大,这个时候,我们就要考虑拆分 bundles。
DllPlugin 和 DLLReferencePlugin 可以实现拆分 bundles,并且可以大大提升构建速度,DllPlugin 和 DLLReferencePlugin 都是 webpack 的内置模块。
我们使用 DllPlugin 将不会频繁更新的库进行编译,当这些依赖的版本没有变化时,就不需要重新编译。我们新建一个 webpack 的配置文件,来专门用于编译动态链接库,例如名为: webpack.config.dll.js,这里我们将 react 和 react-dom 单独打包成一个动态链接库。
在 package.json 的 scripts 中增加:
5、抽离公共代码
抽离公共代码是对于多页应用来说的,如果多个页面引入了一些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。
抽离公共代码对于单页应用和多页应该在配置上没有什么区别,都是配置在optimization.splitChunks 中。
四、代码性能
1、使用事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。
2、if-else 对比 switch
当判断条件数量越来越多时,越倾向于使用 switch 而不是 if-else。
3、查找表
当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可以使用数组和对象来构建。
例:
可以写成 {“0”:”value0”,”1”:”value1”,”2”:”value2”}[index]
4、算法复杂度
在数据量大的应用场景中,需要着重注意算法复杂度问题。
在这个方面可以参考Javascript算法之复杂度分析这篇文章。
5、利用v-if和v-show减少初始化渲染和切换渲染的性能开销
在页面加载时,利用v-if来控制组件仅在首次使用时渲染减少初始化渲染,随后用v-show来控制组件显示隐藏减少切换渲染的性能开销。
6、computed、watch、methods区分使用场景
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
methonds:实时计算,dom每渲染一次都将执行
7、给v-for循环项加上key提高diff计算速度
8、利用v-once处理只会渲染一次的元素或组件
9、利用Object.freeze()冻结不需要响应式变化的数据
10、防抖和节流
防抖:触发事件后规定时间内事件只会执行一次。简单来说就是防止手抖,短时间操作了好多次。
节流:事件在规定时间内只执行一次。
11、组件库的按需引入
12、事件的销毁
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:
13、路由懒加载