每日一篇——23秋招VUE面经(1)

166 阅读7分钟

每日一篇——23秋招VUE面经(1)

⭐Vue2响应式的实现

我的博客里有讲:小何的世界 (theluckyone.top)

⭐Vue2与Vue3的区别

🌙生命周期

Vue3中取消了beforeCreated和created,添加了setup函数,其负责设置组件的初始状态、响应式数据和执行其他逻辑。它在组件实例被创建之前被调用,并且接收两个参数:propscontext

  • props 参数表示组件接收到的属性(props),并且是只读的。
  • context 参数包含了一些常用的属性和方法,如 attrsslotsemit 等。

由于setup就是初始化时执行的函数,所以可以替代beforeCreated和created的效用

常用生命周期对比:

vue2vue3
beforeCreate
created
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

并且再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 可以在异步组件加载完成后执行相关逻辑。

下面是使用 Suspenseasync 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 组件会自动切换到子组件的内容,并显示子组件的内容。

通过使用 Suspenseasync 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 对象和依赖追踪来实现。

  1. Proxy 对象:Vue 3 使用了 ES6 中的 Proxy 对象来实现响应式。Proxy 对象可以拦截 JavaScript 对象上的各种操作,比如读取属性、设置属性、删除属性等。通过使用 Proxy 对象,Vue 3 可以在属性发生变化时捕获到对应的操作,并触发更新。
  2. 依赖追踪:Vue 3 使用了依赖追踪的机制来追踪属性之间的依赖关系。每个响应式对象都有一个关联的依赖追踪器,当访问响应式对象的某个属性时,会将当前的依赖关系添加到依赖追踪器中。这样,在属性发生变化时,可以找到所有依赖于该属性的地方,并触发相应的更新。

🌙新增patchFlag字段

patchFlag 字段是一个位掩码,通过对不同位进行组合来表示不同的变化类型。它可以帮助 Vue 3 在进行 diff 运算时更高效地定位和处理需要更新的部分,避免不必要的比较和操作,从而提升性能。

具体作用如下:

  1. 快速判断节点类型变化:patchFlag 可以告诉 Vue 3 虚拟 DOM 的变化类型,包括节点的类型、文本内容、元素的动态属性等。通过检查 patchFlag 的不同位,可以快速判断节点类型是否发生了变化,避免进行不必要的深度比较。
  2. 提高 diff 粒度:通过细分不同的 patchFlag 值,可以将节点的比较和更新粒度调整得更加准确。例如,如果只有属性发生了变化,可以仅针对属性进行比较,而无需再对子节点进行深度比较。
  3. 优化响应式更新:通过 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 轻量很多。

🌙支持多个根节点