Vue3 支持 vue2 的大多数特性;
更好的支持 Typescript;
谈谈对 vite 的理解,最好对比 webpack 说明
-
webpack 原理图
-
vite 原理图
-
理解:
- webpack 会先打包,然后启动开发服务器,请求服务器时直接给予打包结果;而 vite 是直接启动开发服务器,请求哪个模块再对该模块进行实时编译;
- 由于现代浏览器本身就支持 ES Module,会自动向依赖的 Module 发出请求,vite 充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像 webpack 那样进行打包合并;
- 由于 vite 在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快;当浏览器请求某个模块时,再根据需要对模块内容进行编译;这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite 的优势越明显;
- 在 HMR 方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像 webpack 那样需要把该模块的相关依赖模块全部编译一次,效率更高;
- 当需要打包到生产环境时,vite 使用传统的 rollup 进行打包,因此,vite 的主要优势在开发阶段;另外,由于 vite 利用的是 ES Module ,因此在代码中不可以使用 CommonJS;
效率提升
-
客户端渲染效率比 vue2 提升了 1.3~2 倍;
-
SSR 渲染效率比 vue2 提升了 2~3 倍;
-
面试题:vue3 的效率提升主要表现在以下几个方面;
静态提升
-
下面的静态节点会被提升
- 元素节点
- 没有绑定动态内容
// vue2 的静态节点 render(){ createVNode("h1", null, "Hello World") // ... } // vue3 的静态节点 const hoisted = createVNode("h1", null, "Hello World") function render(){ // 直接使用 hoisted 即可 }
-
静态属性会被提升
htmlJavaScript<div class="user"> {{user.name}} </div>
const hoisted = { class: "user" } function render(){ createVNode("div", hoisted, user.name) // ... }
预字符串化
-
示例代码
<div class="menu-bar-container"> <div class="logo"> <h1>logo</h1> </div> <ul class="nav"> <li><a href="">menu</a></li> <li><a href="">menu</a></li> <li><a href="">menu</a></li> <li><a href="">menu</a></li> <li><a href="">menu</a></li> </ul> <div class="user"> <span>{{ user.name }}</span> </div> </div>
-
当编译器遇到大量连续的静态内容,会直接将其编译为一个普通字符串节点
const _hoisted_2 = _createStaticVNode("<div class=\"logo\"><h1>logo</h1></div><ul class=\"nav\"><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li></ul>")
-
vue2 编译后的虚拟 dom
-
vue3 编译后的虚拟 dom
缓存事件处理函数
-
事件
<button @click="count++">plus</button>
-
vue2 事件函数的处理
// vue2 render(ctx){ return createVNode("button", { // 每次渲染都要重新创建一个事件函数,频繁创建浪费空间 onClick: function($event){ ctx.count++; } }) }
-
vue3 事件函数的处理
// vue3 render(ctx, _cache){ return createVNode("button", { // 事件函数一般不会变化,vue3 将事件函数缓存下来 onClick: cache[0] || (cache[0] = ($event) => (ctx.count++)) }) }
Block Tree
-
示例代码
<form> <div> <label>账号:</label> <input v-model="user.loginId" /> </div> <div> <label>密码:</label> <input v-model="user.loginPwd" /> </div> </form>
-
vue2 的 diff 算法
- vue2 在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层比较,这就浪费了大部分时间在比对静态节点上;
-
vue3 的 diff 算法
- Block Tree 是优化的 diff 算法的核心思想;
- vue3 在对比新旧树的时候,根据 PatchFlag 能判断出哪些是静态节点,哪些是动态节点,然后将动态节点全部放到 form 根节点数组中,每次对比 dom 只需要对比 form 根节点数组中的虚拟节点,不需要对比静态节点,效率大大提升;
PatchFlag
-
示例代码
<div class="user" data-id="1" title="user name"> {{user.name}} </div>
-
vue2 在对比每一个节点时,并不知道这个节点哪些相关信息会发生变化,因此只能将所有信息依次比对;
-
vue3 编译器会记录 节点动态 的部分,利用 PatchFlag 进行标记,对比节点的时候,只需要对比 变化 的部分,也就是被 PatchFlag 标记的部分;
面试题
为什么 vue3 中去掉了 vue 构造函数?
vue2 的全局构造函数带来了诸多问题:
- 调用构造函数的静态方法会对所有 vue 应用生效,不利于隔离不同应用;
- vue2 的构造函数集成了太多功能 ($el、$set、$mount、$delete、$data…) ,不利于 tree shaking ,vue3 把这些功能使用普通函数导出,能够充分利用 tree shaking 优化打包体积;
- vue2 没有把组件实例和 vue 应用两个概念区分开,在 vue2 中,通过 new Vue 创建的对象,既是一个 vue 应用 ,同时又是一个特殊的 vue组件 ;vue3 把两个概念区别开来,通过 createApp 创建的对象,是一个 vue 应用 ,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件;
谈谈你对 vue3 数据响应式的理解
vue3 不再使用 Object.defineProperty 的方式定义完成数据响应式,而是使用 Proxy;
除了 Proxy 本身效率比 Object.defineProperty 更高之外,由于不必递归遍历所有属性,而是直接得到一个 Proxy;所以在 vue3 中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率;
同时,由于 Proxy 可以监控到数组成员的新增和删除,因此,在 vue3 中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在 vue2 中是难以做到的;
Composition API 和 Options API 有何不同?
Composition API 是一组 APl,包括:Reactivity APl、生命周期钩子、依赖注入,使用户可以通过导入函数方式编写 vue 组件,而 0ptions API 则通过声明组件选项的对象形式编写组件;
Composition API 最主要作用是能够简洁、高效复用逻辑,解决了过去 Options API 中 mixins 的各种缺点;
- 另外 Composition API 具有更加敏捷的代码组织能力,很多用户喜欢 0ptions API,认为所有东西都有固定位置的选项放置代码,但是单个组件增长过大之后这反而成为限制,一个逻辑关注点分散在组件各处,形成代码碎片,维护时需要反复横跳,composition API 则可以将它们有效组织在一起;
- 最后 Composition API 拥有更好的类型推断,对 ts 支持更友好,options API 在设计之初并未考虑类型推断因素,虽然官方为此做了很多复杂的类型体操,确保用户可以在使用 Options API、时获得类型推断,然而还是没办法用在 mixins 和 provide/inject 上;
Vue3 首推 Composition API,但是这会让我们在代码组织上多花点心思,因此在选择上,如果项目属于中低复杂度的场景,0ptions API 仍是一个好选择,对于那些大型,高扩展,强维护的项目上,Composition API 会获得更大收益;
vue-router📝 嵌套路由
上一篇