我为什么使用 Vue:Vue 常见面试题整理

谈谈你对 Vue 的理解?

根据官方说法,Vue 是一套用于构建用户界面的渐进式框架。Vue 的设计受到了 MVVM 的启发。Vue 的两个核心是数据驱动组件系统

但我为什么使用 Vue,有以下几个原因:

  • Vue 对于前端初学者比较友好。一个 Vue 文件的结构和原生 HTML 保持了高度相似,分为静态页面,用于放置 html 标签,和 script,用于处理用户操作和业务逻辑,最后是 style 样式,用于书写 CSS 代码,这种写法可以让初学者感到习惯。

  • 其次,Vue 提供了许多 JS 定制化的操作,比如 v-bind 和事件监听的 @ 符号,开发者可以直接使用,从而减少一些重复代码的书写。

  • 最后,就是 Vue 提供一套高效的响应式的系统用于更新 DOM,可以让开发者专注于处理业务而非技术实现。

什么是 MVVM,可以介绍一下吗?

MVVM,即 Model–View–ViewModel,是一种软件架构模式。

  • Model

    即模型,是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。

  • View

    即视图,是用户在屏幕上看到的结构、布局和外观(UI)。

  • ViewModel

    即视图模型,是暴露公共属性和命令的视图的抽象。用于把 ModelView 关联起来。ViewModel 负责把 Model 的数据同步到 View 显示出来,还负责把 View 的修改同步回 Model

MVVM

在 MVVM 架构下,ViewModel 之间并没有直接的联系,而是通过 ViewModel 进行交互,ModelViewModel 之间的交互是双向的,View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。

因此开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

Vue 是如何实现数据双向绑定的?

Vue 实现数据双向绑定主要是采用数据劫持结合发布者-订阅者模式的方式。具体实现就是整合 Observer,Compiler 和 Watcher 三者。

  • Observer

    观察者。Vue 通过 Observer 对数据对象的所有属性进行监听,当把一个普通对象传给 Vue 实例的 data 选项时,Observer 将遍历它的所有属性,并为其添加 gettersettergetter 将收集此属性所有的订阅者,setter 将在属性发生变动的时候,重新为此属性赋值,并通知订阅者调用其对应的更新函数。

    在 Vue 2 中是通过 ES5 的 Object.defineProperty() 方法实现。

    在 Vue 3 中是通过 ES6 的 new Proxy() 实现的。

  • Compiler

    模板编译器。它的作用是对每个元素节点的指令 v- 和模板语法 {{}} 进行扫描,替换对应的真实数据,或绑定相应的事件函数。

  • Watcher

    发布者/订阅者。Watcher 作为连接 Observer 和 Compiler 的桥梁,能够订阅并收到每个属性变动的通知,然后执行相应的回调函数。Compiler 在编译时通过 Watcher 绑定对应的数据更新回调函数,Observer 在监听到数据变化时执行此回调。在 Observer 中,Watcher 就是订阅者,在 Compiler 中,Watcher 就是发布者。

v-model 的原理?

v-model 是 vue 的一个语法糖,它用于监听数据的改变并将数据更新。以 input 元素为例:


其实就等价于


如何在组件中实现 v-model ?

在 Vue 2 组件中实现 v-model,只需定义 model 属性即可。

export default {
  model: {
    prop: "value", // 属性
    event: "input", // 事件
  },
}

在 Vue 3 组合式 API 实现 v-model,需要定义 modelValue 参数,和 emits 方法。

defineProps({
  modelValue: { type: String, default: "" },
})

const emits = defineEmits(["update:modelValue"])

function onInput(val) {
  emits("update:modelValue", val)
}

当数据改变时,Vue 是如何更新 DOM 的?(Diff 算法和虚拟 DOM)

当我们修改了某个数据时,如果直接重新渲染到真实 DOM,开销是很大的。Vue 为了减少开销和提高性能采用了 Diff 算法。当数据发生改变时,Observer 会通知所有 WatcherWatcher 就会调用 patch() 方法(Diff 的具体实现),把变化的内容更新到真实的 DOM,俗称打补丁

Diff 算法会对新旧节点进行同层级比较,当两个新旧节点是相同节点的时候,再去比较他们的子节点(如果是文本则直接更新文本内容),逐层比较然后找到最小差异部分,进行 DOM 更新。如果不是相同节点,则删除之前的内容,重新渲染。

逐层比较

patch() 方法先根据真实 DOM 生成一颗虚拟 DOM,保存到变量 oldVnode,当某个数据改变后会生成一个新的 Vnode,然后 VnodeoldVnode 进行对比,发现有不一样的地方就直接修改在真实 DOM 上,最后再返回新节点作为下次更新的 oldVnode

什么是虚拟 DOM ?

虚拟 DOM(Virtual DOM)就是将真实 DOM 的主要数据抽取出来,并以对象的形式表达。

比如真实 DOM 如下:

123

对应的虚拟 DOM 就是(伪代码):

{
  tag: 'div',
  sel: 'div#id.cls',
  children: [
    { tag: 'h1', text: '123' }
  ]
}

Vue 中的 key 有什么用?

  • 在 Vue 中,key 被用来作为 VNode 的唯一标识。

  • key 主要用在 Vue 的虚拟 DOM Diff 算法,在新旧节点对比时作为识别 VNode 的一个线索。然后找到正确的位置插入或更新节点。如果新旧节点中提供了 key,能更快速地进行比较及复用。反之,Vue 会尽可能复用相同类型元素。

    • {{ item.name }}
  • 手动改变 key 值,可以强制 DOM 进行重新渲染。

    
      {{ text }}
    
    

Vue 3 对 diff 算法进行了哪些优化

在 Vue 2 中,每当数据发生变化时,Vue 会创建一个新的虚拟 DOM 树,并对整个虚拟 DOM 树进行递归比较,即使其中大部分内容是静态的,最后再找到不同的节点,然后进行更新。

Vue 3 引入了静态标记的概念,通过静态标记,Vue 3 可以将模板中的静态内容和动态内容区分开来。这样,在更新过程中,Vue 3 只会关注动态部分的比较,而对于静态内容,它将跳过比较的步骤,从而避免了不必要的比较,提高了性能和效率。

Vue 实例的生命周期钩子都有哪些?

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

Vue 2 有以下钩子:

  • beforeCreate

    实例初始化之前,$eldata 都为 undefined

  • created

    实例创建完成,data 已经绑定。

  • beforeMount