从面试题入手,畅谈 Vue 3 性能优化
前言
今年又是一个非常寒冷的冬天,很多公司都开始人员精简。市场从来不缺前端,但对高级前端的需求还是特别强烈的。一些大厂的面试官为了区分候选人对前端领域能力的深度,经常会在面试过程中考察一些前端框架的源码性知识点。Vuejs
作为世界顶尖的框架之一,几乎在所有的面试场景中或多或少都会被提及。
笔者之前在蚂蚁集团就职,对于 Vue 3
的考点还是会经常问的。接下来,我将根据多年的面试以及被面试经验,为小伙伴们梳理最近大厂爱问的 Vue 3
问题。然后我们再根据问题举一反三,深入学习 Vue 3
源码知识!
场景一:Vue 3.x 相对于 Vue 2.x 做了那些额外的性能优化?
要理解 Vue 3
的性能优化的核心,就需要了解 Vuejs
的核心设计理念。我们知道 Vuejs
官网上有一句话总结的特别到位:渐进式 JavaScript 框架,易学易用,性能出色,适用于场景丰富的 Web 框架。 其实我们的答案就蕴藏在这句话里。
首先,我们知道当我们浏览 Web
网页时,有两类场景会制约 Web
网页的性能
- 网络传输的瓶颈
- CPU的瓶颈
所以要回答这个问题,就可以直接从这两方面入手。
网络传输的瓶颈优化
对于前端框架而言,制约网络传输的因素最大的就是代码体积,代码体积越大,传输效率越慢。尤其对于 SPA
单页应用的 CSR
(客户端渲染) 而言。一个大体积的框架资源,就意味着用户需要等待白屏的时间越长。而 Vue 3
在减少源码体积方面做的最多的就是通过精细化的 Tree-Shacking
机制来构建 渐进式
代码。
1. /*#__PURE__*/
标记
我们知道 Tree-Shaking
可以删除一些 DC(dead code)
代码。但是对于一些有副作用的函数代码,却是无法进行很好的识别和删除,举个例子:
foo()
function foo(obj) {
obj?.a
}
上述代码中,foo
函数本身是没有任何意义的,仅仅是对对象 obj
进行了属性 a
的读取操作,但是 Tree-Shaking
是无法删除该函数的,因为上述的属性读取操作可能会产生副作用,因为 obj
可能是一个响应式对象,我们可能对 obj
定了一个 getter
在 getter
中触发了很多不可预期的操作。
如果我们确认 foo
函数是一个不会有副作用的纯净的函数,那么这个时候 /*#__PURE__*/
就派上用场了,其作用就是告诉打包器,对于 foo
函数的调用不会产生副作用,你可以放心地对其进行 Tree-Shaking
。
另外,值得一提的是,在 Vue 3
源码中,包含了大量的 /*#__PURE__*/
标识符,可见 Vue 3
对源码体积的控制是多么的用心!
2. 特性开关
在 Vue 3
源码中的 rollup.config.mjs
中有这样一段代码:
{
__FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
}
其中 __FEATURE_OPTIONS_API__
是一个构建时的环境变量,我们知道 Vue 3
在某些 API
方面是兼容 Vue 3
写法的,比如 Options API
。但是如果我们在项目中仅仅使用 Compositon API
而不想使用 Options API
那么我们就可以在项目构建时关闭这个选项,从而减少代码体积。我们看看这个变量在 Vue 3
源码中是如何使用的:
// 兼容 2.x 选项式 API
if (__FEATURE_OPTIONS_API__) {
currentInstance = instance
pauseTracking()
applyOptions(instance, Component)
resetTracking()
currentInstance = null
}
用户可以通过设置 __VUE_OPTIONS_API__
预定义常量的值来控制是否要包含这段代码。通常用户可以使用 webpack.DefinePlugin
插件来实现:
// webpack.DefinePlugin 插件配置
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: JSON.stringify(true) // 开启特性
})
除此之外,类似的开发环境会通过 __DEV__
来输出告警规则,而在生产环境剔除这些告警降低构建后的包体积都是类似的手段:
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
CPU 瓶颈优化
当项目变得庞大、组件数量繁多时,就容易遇到CPU的瓶颈。主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。
我们知道,JS可以操作DOM,GUI渲染线程
与JS线程
是互斥的。所以JS脚本执行和浏览器布局、绘制不能同时执行。
在每16.6ms时间内,需要完成如下工作:
JS脚本执行 ----- 样式布局 ----- 样式绘制
当JS执行时间过长,超出了16.6ms,这次刷新就没有时间执行样式布局和样式绘制了,也就出现了丢帧的情况,会发生卡顿。
为了解决庞大元素组件渲染、更新卡顿的问题,Vue
的策略是一方面采用了组件级的细粒度更新,控制更新的影响面:Vue 3
中,每个组件都会生成一个渲染函数,这些渲染函数执行时会进行数据访问,此时这些渲染函数被收集进入副作用函数中,建立数据 -> 副作用
的映射关系。当数据变更时,再触发副作用函数的重新执行,即重新渲染。
另一方面则在编译器中做了大量的静态优化,得益于这些优化,才让我们可以 易学易用的写出性能出色的 Vue 项目。
下面简单介绍几种编译时优化策略:
1. 靶向更新
假设有以下模板:
<template>
<p>hello world</p>
<p>{{ msg }}</p>
</template>>
其中一个 p
标签的节点是一个静态的节点,第二个 p
标签的节点是一个动态的节点,如果当 msg
的值发生了变化,那么理论上肉眼可见最优的更新方案应该是只做第二个动态节点的 diff
而无需进行第一个 p
标签节点的 diff
。
上述模版转成 vnode
后的结果大致为:
const vnode = {
type: Symbol(Fragment),
children: [
{ type: 'p', children: 'hello world' },
{ type: 'p', children: ctx.msg, patchFlag: 1 /* 动态的 text */ },
],
dynamicChildren: [
{ type: 'p', children: ctx.msg, patchFlag: 1 /* 动态的 text */ },
]
}
此时组件内存在了一个静态的节点 <p>hello world</p>
,在传统的 diff
算法里,还是需要对该静态节点进行不必要的 diff
。
而 Vue3
则是先通过 patchFlag
来标记动态节点 <p>{{ msg }}</p>
然后配合 dynamicChildren
将动态节点进行收集,从而完成在 diff
阶段只做靶向更新的目的。
2. 静态提升
接下来,我们再来说一下,为什么要做静态提升呢? 如下模板所示:
<div>
<p>text</p>
</div>
在没有被提升的情况下其渲染函数相当于:
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("p", null, "text")
]))
}
很明显,p
标签是静态的,它不会改变。但是如上渲染函数的问题也很明显,如果组件内存在动态的内容,当渲染函数重新执行时,即使 p
标签是静态的,那么它对应的 VNode
也会重新创建。
所谓的 “静态提升”,就是将一些静态的节点或属性提升到渲染函数之外。如下面的代码所示:
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "text", -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
这就实现了减少 VNode
创建的性能消耗。
而这里的静态提升步骤生成的 hoists
,会在 codegenNode
会在生成代码阶段帮助我们生成静态提升的相关代码。
预字符串化
Vue 3
在编译时会进行静态提升节点的 预字符串化
。什么是预字符串化呢?一起来看个示例:
<template>
<p></p>
... 共 20+ 节点
<p></p>
</template>
对于这样有大量静态提升的模版场景,如果不考虑 预字符串化
那么生成的渲染函数将会包含大量的 createElementVNode
函数:假设如上模板中有大量连续的静态的 p
标签,此时渲染函数生成的结果如下:
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, null, -1 /* HOISTED */)
// ...
const _hoisted_20 = /*#__PURE__*/_createElementVNode("p", null, null, -1 /* HOISTED */)
const _hoisted_21 = [
_hoisted_1,
// ...
_hoisted_20,
]
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_21))
}
createElementVNode
大量连续性创建 vnode
也是挺影响性能的,所以可以通过 预字符串化
来一次性创建这些静态节点,采用 与字符串化
后,生成的渲染函数如下:
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<p></p>...<p></p>", 20)
const _hoisted_21 = [
_hoisted_1
]
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_21))
}
这样一方面降低了 createElementVNode
连续创建带来的性能损耗,也侧面减少了代码体积。
小结
本小节为大家解读了部分 Vue 3
性能优化的设计,更多的内容可以参考作者写的小册:《Vue 3 技术揭秘》。
接下来的文章将继续为大家解读 Vue 3
响应式设计原理和异步调度更新策略。
推广自己的小册
如果你对 Vue 3
感兴趣,想去深耕一下 Vue 3
相关的设计理念,但是直接去啃 Vue 3
源码会非常晦涩难懂,比如一个 baseCreateRenderer
函数就有接近 2000
行代码,可能会让你半途而废。
作者花了 3
个多月的时间呕心沥血的写了一个小册《Vue 3 技术揭秘》将会为您从头到尾的介绍 Vue 3
的优秀设计!
小册一方面会对 Vue 3 核心源码做适量的精简
,让你可以只用关注核心逻辑实现;另一方面,也配了大量的插图
,一图胜千言,可以更加生动地向你展示源码的运行机制。
本小册主要划分为了 5 大模块
来依次为你揭开 Vue 3
的“神秘面纱”。
模块一:渲染器实现原理。从根组件初始化开始,一步步介绍组件实例化、完整更新、
diff
过程等。模块二:响应式原理。核心介绍
Vue 3
基于Proxy
实现的响应式原理,深入解读依赖收集过程、响应式触达过程和相关联的watch、computed、inject/provide
函数实现以及异步批量更新原理。在学习的过程中,你会渐进式体会到与 Vue 2 响应式原理的差异
以及异步批量更新的不同之处。模块三:编译器实现原理。重点讲解模板是如何被一步步编译成渲染函数的,以及在编译时
Vue 3
所做的大量编译时优化的工作。模块四:内置组件实现原理。主要介绍
Vue 3
几个常用的内置组件:Transition、KeepAlive、Teleport 、Suspense
相关的组件运行机制和实现原理。模块五:特殊元素&指令。重点分析
v-model
是如何实现双向数据绑定的,以及slot
插槽是如何实现内容分发的。
为方便你理解,我整理出来了如下的思维导图:
相信掌握了本小册这些模块的核心原理之后,你再去阅读 Vue 3 源码或者是解决 Vue 3 的疑难杂症时,会更加得心应手。
从面试题入手,畅谈 Vue 3 性能优化的更多相关文章
- Vue常用性能优化
Vue常用性能优化 Vue常用的一些优化方式,主要是在构建项目过程需要注意的方面. 编码优化 避免响应所有数据 不要将所有的数据都放到data中,data中的数据都会增加getter和setter,并 ...
- vue页面性能优化方案
个人在项目中用到的页面性能优化的方式总结. 一.均衡页面加载文件的大小和数量 1.项目中小图片图片转base64,通过工具如webpack进行图片压缩,文件进行压缩混淆等 2.vue-router 懒 ...
- Vue项目性能优化整理
以下方式基于 @vue/cli 快速搭建的交互式项目脚手架 1. 路由懒加载 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分割成不同的代码块,然 ...
- 【Vuejs】335-(超全) Vue 项目性能优化实践指南
点击上方"前端自习课"关注,学习起来~ 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 D ...
- vue项目性能优化总结
在使用elementUI构建公司管理系统时,发现首屏加载时间长,加载的网络资源比较多,对系统的体验性会差一点,而且用webpack打包的vuejs的vendor包会比较大.所以通过搜集网上所有对于vu ...
- vue项目性能优化,优化项目加载慢的问题
一. 对路由组件进行懒加载: 如果使用同步的方式加载组件,在首屏加载时会对网络资源加载加载比较多,资源比较大,加载速度比较慢.所以设置路由懒加载,按需加载会加速首屏渲染.在没有对路由进行懒加载时,在C ...
- XCel 项目总结 - Electron 与 Vue 的性能优化
XCEL 是由凹凸实验室推出的一个 Excel 数据清洗工具,其通过可视化的方式让用户轻松地对 Excel 数据进行筛选. XCEL 基于 Electron 和 Vue 2.0 进行开发,充分利用 E ...
- 浅谈Vue 项目性能优化 经验
我优化公司的项目总结的几点: 1.先查看引入的图片大小,如果太大了,可以压缩,压缩路径:https://zhitu.isux.us/ 2.代码包优化, 待下项目开发完成.进行打包源码上线环节,需要对项 ...
- vue项目性能优化(路由懒加载、gzip加速、cdn加速)
前端工程性能优化一说意义深远悠长,本章主要介绍除了一些基础优化外如何实行路由懒加载.Gzip加速.CDN加速,让网页飞的快一些. 基础优化 老生常谈的一些: 不要在模板中写复杂的表达式 慎用watch ...
- VUE项目性能优化实践——通过懒加载提升页面响应速度
本文由葡萄城技术团队原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 最近我司因业务需求,需要在一个内部数据分析平台集成在线Excel功能,既然我 ...
随机推荐
- Notebook交互式完成目标检测任务
摘要:本文将介绍一种在Notebook中进行算法开发的新方式,新手也能够快速训练自己的模型. 目标检测是计算机视觉中非常常用且基础的任务,但是由于目标检测任务的复杂性,往往令新手望而却步.本文将介绍一 ...
- JSP实现登录删除添加星座等(带样式)
功能要求 1.完成两个页面 2.第一个登陆页面login. jsp 3.第二个用户管理页面useManage. jsp 4.有登录功能(能进行用户名密码的校验,用户名若为自己的学号密码为班级号,允许登 ...
- GCN的原理及其代码实现
图数据的特征性质 图像数据是一种特殊的图数据,图像数据是标准的2D网格结构图数据.图像数据的CNN卷积神经网络算法不能直接用在图数据上,原因是图数据具有以下特殊性. 节点分布不均匀:图像数据及网格数据 ...
- .NET 7 RC 2 发布,倒计时一个月发布正式版
微软2022-10-22 发布了 .NET 7 RC 2,下一站是.NET 7正式发布,就在下个月Net Conf 2022(11月8日)期间正式发布. 经过长达一年时间的开发,.NET 7 规划的所 ...
- JPA入门学习集合springboot(一)
1.在pom.xml文件中添加相应依赖 SpringData jpa和数据库MySql <!-- Spring Data JPA 依赖(重要) --> <dependency> ...
- postman一些你不常用的实用技巧,竟然还能这么玩
序言 各位好啊,我是会编程的蜗牛,作为java开发者,平时调试接口的时候,肯定需要用到接口调试工具,或者Swagger之类的.Swagger的优势在于它可以将后台加的一些接口注释信息直接展示出来,但是 ...
- python环境安装(pyhon和pycharm)
一.python安装 在地址栏输入https://www.python.org/进入python官网, 点击windows后会出现各种可供下载的历史版本, 安装包下载后,双击运行 点击下一步 勾选下面 ...
- 如何实现一个SQL解析器
作者:vivo 互联网搜索团队- Deng Jie 一.背景 随着技术的不断的发展,在大数据领域出现了越来越多的技术框架.而为了降低大数据的学习成本和难度,越来越多的大数据技术和应用开始支持SQL进 ...
- web3.0、比特币、区块链、元宇宙,以及那些待收割的韭菜们!
前几天看到周星驰在社交账号上招聘web3.0的人才,感觉有必要说说web3.0,当然不是基于技术层面,而是从另一个维度说说web3.0以及其它相关的概念,从而做到如何反欺诈,如何避免被资本割韭菜.想到 ...
- 每日算法3:随机生成五个不同整数,将数字转换为RMB格式
随机生成五个不同整数 点击查看代码 /* 题目解析: 1.采用Math对象的random()方法, 2.将每次生成的数跟之前的数判断相等则此次生成无效i-- */ function randomNum ...