文章目录:
- 一、代码层面的优化
- 1.1、v-if 和 v-show 区分使用场景
- 1.2、computed 和 watch 区分使用场景
- 1.3、v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 1.4、长列表性能优化
- 1.5、事件的销毁
- 1.6、图片资源懒加载
- 1.7、路由懒加载
- 1.8、第三方插件的按需引入
- 1.9、优化无限列表性能
- 1.10、服务端渲染 SSR or 预渲染
- 二、Webpack 层面的优化
- 2.1、Webpack 对图片进行压缩
- 2.2、减少 ES6 转为 ES5 的冗余代码
- 2.3、提取公共代码
- 2.4、模板预编译
- 2.5、提取组件的 CSS
- 2.6、优化 SourceMap
- 2.7、构建结果输出分析
- 2.8、Vue 项目的编译优化
- 三、基础的 Web 技术优化
- 3.1、开启 gzip 压缩
- 3.2、浏览器缓存
- 3.3、使用CDN (内容分发网络)
- 3.4、减少 HTTP 请求数
- 3.5、压缩 JavaScript 和 CSS
- 四、非框架代码优化
- 4.1、减少重绘、重排
- 4.2、将 CSS 放在页面顶部
- 4.3、 将 JavaScript 放在页面底部
- 4.4、减少 DNS 查询
主要思路:
- Vue 代码层面的优化;
- webpack 配置层面的优化;
- 基础的 Web 技术层面的优化。
- 非框架代码优化
一、代码层面的优化
1.1、v-if 和 v-show 区分使用场景
- 手段:
v-if
是动态的向DOM树内添加或者删除DOM元素;v-show
是通过设置DOM元素的display样式属性控制显隐; - 编译过程:
v-if
切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show
只是简单的基于css切换; - 编译条件:
v-if
是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载);
v-show
是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留; - 性能消耗:
v-if
有更高的切换消耗;v-show
有更高的初始渲染消耗;
1.2、computed 和 watch 区分使用场景
computed
是具有缓存的,这就意味着只要计算属性的依赖没有进行相应的数据更新,那么computed
会直接从缓存中获取值,多次访问都会返回之前的计算结果
1.3、v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
(1)v-for 遍历必须为 item 添加 key
在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。
(2)v-for 遍历避免同时使用 v-if
v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性。
// 推荐:
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
1.4、长列表性能优化
Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
1.5、事件的销毁
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:
created() {
addEventListener('click', this.click, false)
},
beforeDestroy() {
removeEventListener('click', this.click, false)
}
1.6、图片资源懒加载
1.7、路由懒加载
这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
1.8、第三方插件的按需引入
1.9、优化无限列表性能
1.10、服务端渲染 SSR or 预渲染
二、Webpack 层面的优化
2.1、Webpack 对图片进行压缩
小的图片转化为 base64 格式;对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片
2.2、减少 ES6 转为 ES5 的冗余代码
2.3、提取公共代码
将每个页面的第三方库和公共模块提取出来
2.4、模板预编译
2.5、提取组件的 CSS
2.6、优化 SourceMap
2.7、构建结果输出分析
Vue 项目中用到的分析工具:webpack-bundle-analyzer 。
2.8、Vue 项目的编译优化
三、基础的 Web 技术优化
3.1、开启 gzip 压缩
压缩组件通过减少 HTTP 请求产生的响应包的大小,从而降低传输时间的方式来提高性能。从 HTTP1.1 开始,Web 客户端可以通过 HTTP 请求中的 Accept-Encoding
头来标识对压缩的支持:
Accept-Encoding: gzip,deflate
Chrome 开发者工具栏监控到如图:
如果 Web 服务器看到请求中的这个头,就会使用客户端列出的方法中的一种来压缩响应。Web 服务器通过响应中的 Content-Encoding
头来告知 Web 客户端:
Content-Encoding: gzip
Chrome 开发者工具栏监控到如图:
压缩通常能将响应的数据量减少近 70%,但是压缩通常情况下会带来服务端和客户端的 CPU 开销,要检测受益是否大于开销,需要综合考虑响应大小、带宽和客户端服务端物理距离等因素。通常需要对大于 1KB 或 2KB 的文件进行压缩。
当浏览器通过代理来发送请求时,有可能出现浏览器期望接受的压缩后内容和实际接收到的不一致的情况。解决这一问题的方法是在 Web 服务器的响应中添加 Vary 头。Web 服务器可以告诉代理根据一个或多个请求头来改变缓存的响应。由于压缩的决定是基于 Accept-Encoding
请求头的,因此需要在服务器的 Vary 响应头中包含 Accept-Encoding:
Vary: Accept-Encoding
目前大约 90% 的通过浏览器进行的网络通信都需要使用 gzip,这使得服务端和客户端的对等性变得额外重要。无论是客户端还是服务端发送错误,都会造成页面被破坏。避免错误的一种方式是采用『浏览器白名单』方式,即只为经过证实支持压缩的浏览器提供压缩内容,但是当代理缓存加进来以后,处理边缘情形浏览器将变得更加复杂。另一种方式是使用 Vary: *
或 Cache-Control: private
头来禁用代理缓存。此种方式会为所有浏览器禁用代理缓存,从而增加带宽开销。如何平衡压缩和代理支持需要在加快响应时间、减小带宽开销和边缘情形浏览器缺陷之间进行权衡。
- 如果网站的用户很少,并且他们处于一个小圈子中,边缘情形浏览器不需要太多关注,可以压缩内容并使用
Vary: Accept-Encoding
。- 如果更注重带宽开销,可以和前一种情形一样,压缩内容并使用
Vary: Accept-Encoding
。- 如果网站拥有大量的、多变的用户群,能够应付较高的带宽开销,并且享有高质量的声誉,需要压缩内容并使用
Cache-Control:Private。
( Google 和 Yahoo 都使用这种方式)
3.2、浏览器缓存
强缓存,对比缓存
(1)已存在缓存数据时,仅基于强制缓存,请求数据的流程如下所示:
(2)已存在缓存数据时,仅基于对比缓存,请求数据的流程如下所示:
强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互。
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。
3.3、使用CDN (内容分发网络)
3.4、减少 HTTP 请求数
减少http的请求次数,将多个请求合并成同一个,减少http的开销。
3.5、压缩 JavaScript 和 CSS
四、非框架代码优化
4.1、减少重绘、重排
在优化 Web 性能的方法中,减少重绘、重排是一种很好的优化方式。减少重绘、重排措施:
- 使用 class 操作样式,而不是频繁操作 style
- 避免使用 table 布局
- 批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React
- Debounce window resize 事件
- 对 dom 属性的读写要分离
- will-change: transform 做优化
4.2、将 CSS 放在页面顶部
关注性能的前端工程师希望页面被逐步渲染,这时因为,我们希望浏览器尽早渲染获取到的任何内容。这对大页面和网速慢的用户很重要。给用户视觉反馈,比如进度条的重要性已经被大量研究和记录。在我们的情况中,HTML 页面就是进度条。当浏览器逐步加载页面头部,导航条,logo 等等,这些都是给等待页面的用户的视觉反馈。这优化了整体用户体验。
把样式表放在文档底部的问题是它阻止了许多浏览器的逐步渲染,包括 IE。这些浏览器阻止渲染来避免在样式更改时需要重绘页面元素。所以用户会卡在白屏。
HTML 规范 清楚表明样式应该在 里。
4.3、 将 JavaScript 放在页面底部
脚本会阻塞并行下载,HTTP/1.1 官方文档建议浏览器每个主机名下并行下载的组件数不要超过两个,如果图片来自多个域名,并行下载的数量就可以超过两个。如果脚本正在下载,浏览器就不开始任何其它下载任务,即使是在不同域名下的。
有时候,并不容易把脚本移动到底部。举个例子,如果脚本是用 document.write 插入到页面内容中的,就没办法再往下移了。还可能存在作用域问题,在多数情况下,这些问题都是可以解决的。
一个常见的建议是用推迟(deferred)脚本,有 DEFER 属性的脚本意味着不能含有 document.write,并且提示浏览器告诉他们可以继续渲染。不幸的是,Firefox 不支持 DEFER 属性。在 IE 中,脚本可能被推迟,但不尽如人意。如果脚本可以推迟,我们就可以把它放到页面底部,页面就可以更快地载入。
4.4、减少 DNS 查询
详细见另一篇文章