每日一篇——23秋招VUE面经(1)
⭐Vue2响应式的实现
我的博客里有讲:小何的世界 (theluckyone.top)
⭐Vue2与Vue3的区别
🌙生命周期
Vue3中取消了beforeCreated和created,添加了setup函数,其负责设置组件的初始状态、响应式数据和执行其他逻辑。它在组件实例被创建之前被调用,并且接收两个参数:props 和 context:
props参数表示组件接收到的属性(props),并且是只读的。context参数包含了一些常用的属性和方法,如attrs、slots、emit等。
由于setup就是初始化时执行的函数,所以可以替代beforeCreated和created的效用
常用生命周期对比:
| vue2 | vue3 |
|---|---|
| beforeCreate | |
| created | |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
并且再Vue3中提供了onServerPrefetch钩子,若在其中返回了一个Promise,服务端就可以等待Promise完成后再对该组件进行渲染
🌙组合式API
Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
🌙异步组件加载
在 Vue 3 中,异步组件加载可以使用 Suspense 组件和 async setup 功能来实现。Suspense 组件可以用于处理异步组件的加载和显示占位符内容,而 async setup 可以在异步组件加载完成后执行相关逻辑。
下面是使用 Suspense 和 async setup 的示例:
首先,在父组件中使用 Suspense 包裹需要异步加载的子组件,并设置一个占位符(fallback)内容:
<template>
<Suspense>
<template #default>
<!-- 异步加载的子组件 -->
<AsyncComponent />
</template>
<template #fallback>
<!-- 加载时的占位符内容 -->
<div>Loading...</div>
</template>
</Suspense>
</template>
然后,在异步加载的子组件中使用 async setup 来定义组件的逻辑:
export default {
async setup() {
// 异步加载资源或执行其他异步操作
await someAsyncOperation();
// 返回组件的响应式数据和方法
return {
data: reactive({}),
method() {
// ...
}
};
}
};
</script>
在上述示例中,当父组件渲染时,Suspense 组件会检测到子组件的异步加载,并显示占位符内容("Loading...")。一旦异步加载完成,Suspense 组件会自动切换到子组件的内容,并显示子组件的内容。
通过使用 Suspense 和 async setup,你可以更方便地处理异步组件加载过程中的状态,并在加载完成后执行相关逻辑。
🌙Teleport
传统的 Vue 组件会被渲染到其父组件的 DOM 结构内,但是有时我们希望将组件渲染到 DOM 结构的不同位置,而不需要改变其父组件的布局。这就是 Teleport 的作用所在。
下面是一个使用 Teleport 的示例:
<template>
<div>
<!-- 在页面的 body 元素下渲染 Modal 组件 -->
<Teleport to="body">
<Modal />
</Teleport>
<!-- 其他组件的内容 -->
<div>
...
</div>
</div>
</template>
在上述示例中,Teleport 组件将 Modal 组件渲染到了页面的 body 元素下。无论在组件内部的哪个位置使用 Teleport,最终 Modal 组件都会被渲染到 body 元素下,而不是组件的父容器内部。
通过使用 Teleport,你可以将组件渲染到任何合法的 DOM 元素位置,而不局限于其父组件的 DOM 结构。这对于实现全局的弹窗、模态框或通知等功能非常有用。
需要注意的是,Teleport 需要配合 to 属性来指定组件渲染的目标位置,该位置可以是一个有效的 CSS 选择器,或者是特殊的预定义值,如 "body"、"#app" 等。
另外,需要确保目标位置在组件被挂载的时候是存在的,否则 Teleport 会抛出警告并在渲染期间失效。
总结来说,Teleport 组件使你能够非常灵活地将组件渲染到任意位置,而不受其父组件的限制。这为处理全局弹窗、模态框等提供了方便的解决方案。
🌙响应式原理
vue2的响应式原理在上面提过了
Vue 3 的底层响应式实现原理主要通过 Proxy 对象和依赖追踪来实现。
- Proxy 对象:Vue 3 使用了 ES6 中的 Proxy 对象来实现响应式。Proxy 对象可以拦截 JavaScript 对象上的各种操作,比如读取属性、设置属性、删除属性等。通过使用 Proxy 对象,Vue 3 可以在属性发生变化时捕获到对应的操作,并触发更新。
- 依赖追踪:Vue 3 使用了依赖追踪的机制来追踪属性之间的依赖关系。每个响应式对象都有一个关联的依赖追踪器,当访问响应式对象的某个属性时,会将当前的依赖关系添加到依赖追踪器中。这样,在属性发生变化时,可以找到所有依赖于该属性的地方,并触发相应的更新。
🌙新增patchFlag字段
patchFlag 字段是一个位掩码,通过对不同位进行组合来表示不同的变化类型。它可以帮助 Vue 3 在进行 diff 运算时更高效地定位和处理需要更新的部分,避免不必要的比较和操作,从而提升性能。
具体作用如下:
- 快速判断节点类型变化:
patchFlag可以告诉 Vue 3 虚拟 DOM 的变化类型,包括节点的类型、文本内容、元素的动态属性等。通过检查patchFlag的不同位,可以快速判断节点类型是否发生了变化,避免进行不必要的深度比较。 - 提高 diff 粒度:通过细分不同的
patchFlag值,可以将节点的比较和更新粒度调整得更加准确。例如,如果只有属性发生了变化,可以仅针对属性进行比较,而无需再对子节点进行深度比较。 - 优化响应式更新:通过
patchFlag标记节点的变化,Vue 3 可以针对性地处理不同类型的更新。例如,当一个组件的props发生变化时,只需针对props进行更新,而无需重新渲染整个组件。
类型列举:
// patchFlags 字段类型列举
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 动态类名
STYLE = 1 << 2, // 动态样式
PROPS = 1 << 3, // 动态属性,不包含类名和样式
FULL_PROPS = 1 << 4, // 具有动态 key 属性,当 key 改变,需要进行完整的 diff 比较
HYDRATE_EVENTS = 1 << 5, // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 带有 key 属性的 fragment 或部分子节点
UNKEYED_FRAGMENT = 1 << 8, // 子节点没有 key 的fragment
NEED_PATCH = 1 << 9, // 只会进行非 props 的比较
DYNAMIC_SLOTS = 1 << 10, // 动态的插槽
HOISTED = -1, // 静态节点,diff阶段忽略其子节点
BAIL = -2 // 代表 diff 应该结束
}
🌙事件缓存
Vue3 的cacheHandler可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。
例如我们下面加一个 click 事件:
js复制代码<div id="app">
<h1>vue3事件缓存讲解</h1>
<p>今天天气真不错</p>
<div>{{name}}</div>
<span onCLick=() => {}><span>
</div>
渲染函数如下所示。
js复制代码import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue
const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: app }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1, null, vue3事件缓存讲解, -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 今天天气真不错, -1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(span, { onCLick: () => {} }, [
/*#__PURE__*/_createElementVNode(span)
], -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(div, _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode(div, null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_4
]))
}
观察以上渲染函数,你会发现 click 事件节点为静态节点(HOISTED 为 -1),即不需要每次重新渲染。
🌙diff算法优化
patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点。一定程度地减少节点本身及其属性的比对。
🌙打包优化
Tree-shaking:模块打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。
以 nextTick 为例子,在 Vue2 中,全局API暴露在Vue实例上,即使未使用,也无法通过 tree-shaking 进行消除。
import Vue from 'vue';
Vue.nextTick(() => {
// 一些和DOM有关的东西
});
Vue3 中针对全局和内部的API进行了重构,并考虑到 tree-shaking 的支持。因此,全局API现在只能作为ES模块构建的命名导出进行访问。
import { nextTick } from 'vue'; // 显式导入
nextTick(() => {
// 一些和DOM有关的东西
});
通过这一更改,只要模块绑定器支持 tree-shaking,则Vue应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小。
受此更改影响的全局API如下所示。
- Vue.nextTick
- Vue.observable (用 Vue.reactive 替换)
- Vue.version
- Vue.compile (仅全构建)
- Vue.set (仅兼容构建)
- Vue.delete (仅兼容构建)
内部API也有诸如 transition、v-model 等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3 将所有运行功能打包也只有约22.5kb,比 Vue2 轻量很多。