Vue 3 常见面试题汇总【持续更新中】

2023-08-16  本文已影响0人  limengzhe

前言

最近两年许多大厂都在实行“降本增效”、“优化组织架构”,然后 “为社会输送了大量人才”,今年(2023)更是不容易,一些外资企业也陆续撤离,各行各业订单大量减少,业务大量裁撤,导致工作岗位大幅度减少。程序员们不得不重新找工作,有人回到了老家做起了生意,有人送起了外卖,有人跑起了网约车。当然也有不少人选择继续坚持,不管做出了什么选择,各位的选择都值得被尊重。在这个时代环境下,大家都是好样的。

而我选择了继续坚持,最近也在找工作的路上,总结了一些面试题,如果你的技术栈是 Vue,或者需要从 React 转到 Vue,希望这篇面试题能帮到你。

谈谈你对 Vue 的理解?为什么选择 Vue?

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

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

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

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

MVVM

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

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

Vue 响应式系统的原理

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

为什么 Vue 3.x 采用了 Proxy 抛弃了 Object.defineProperty() ?

Vue 是如何实现数据双向绑定的?v-model 的原理?

Vue 组件可以通过使用 v-model 指令以实现双向绑定。v-model 是 vue 的一个语法糖,它用于监听数据的改变并将数据更新。以 input 元素为例:

<el-input v-model="foo" />

其实就等价于

<input :value="searchText" @input="searchText = $event.target.value" />

如何在组件中实现 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 操作。虚拟 DOM 的主要目的是提高性能和减少实际 DOM 操作的次数,从而改善用户界面的渲染速度和响应性。

比如真实 DOM 如下:

<div id="hello">
  <h1>123</h1>
</div>

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

const vnode = {
  type: "div",
  props: {
    id: "hello",
  },
  children: [
    {
      type: "h1",
      innerText: "123",
    },
  ],
}

Vue 中的 key 有什么用?

watch 和 computed 分别是做什么的?有何区别?

watch 和 computed 都可以用于监听数据,区别是使用场景不同,watch 用于监听一个数据,当数据改变时,可以执行传入的回调函数:

<script setup>
  import { reactive, watch } from "vue"

  const state = reactive({ count: 0 })
  watch(
    () => state.count,
    (count, prevCount) => {
      // do something
    }
  )
</script>

computed 用于返回一个新的数据,在 Vue 3.x 中会返回一个只读的响应式 ref 对象。但是可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

<script setup>
  import { reactive, computed } from "vue"

  const state = reactive({ numA: 1, numB: 2 })
  const plusOne = computed(() => state.numA + state.numB)

  console.log(plusOne.value) // 3
</script>

有些人会提到 computed 支持缓存,不支持异步,也是和 watch 的区别。

但这里要告诉大家的是,computed 本身的设计就是为了计算,而非异步的获取一个数据,详情请参考官网

至于缓存,这同样属于 computed 的特性,它支持缓存,这是和调用普通函数的区别,而不应该和 watch 进行比较,watch 本身用于监听数据变化,在根本上不存在缓存的概念。

Vue 3.x 带来了哪些新的特性和性能方面的提升?

  1. 引入了 Composition API(组合式 API)。允许开发者更灵活地组织和重用组件逻辑。它使用函数而不是选项对象来组织组件的代码,使得代码更具可读性和维护性。

  2. 多根组件。可以直接在 template 中使用多个根级别的元素,而不需要额外的包装元素。这样更方便地组织组件的结构。

  3. 引入了 Teleport(传送)。可以将组件的内容渲染到指定 DOM 节点的新特性。一般用于创建全局弹窗和对话框等组件。

  4. 响应式系统升级。从 defineProperty 升级到 ES2015 原生的 Proxy,不需要初始化遍历所有属性,就可以监听新增和删除的属性。

  5. 编译优化。重写了虚拟 DOM,提升了渲染速度。diff 时静态节点会被直接跳过。

  6. 源码体积优化。移除了一些非必要的特性,如 filter,一些新增的模块也将会被按需引入,减小了打包体积。

  7. 打包优化。更强的 Tree Shaking,可以过滤不使用的模块,没有使用到的组件,比如过渡(transition)组件,则打包时不会包含它。

Vue 3 移除了哪些特性

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

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

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

<div>
  <!-- 需静态提升 -->
  <div>foo</div>
  <!-- 需静态提升 -->
  <div>bar</div>
  <div>{{ dynamic }}</div>
</div>

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

生命周期钩子是指一个组件实例从创建到卸载(销毁)的全过程,例如,设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。在这个过程中会运行一些叫做生命周期钩子的函数,从而可以使开发者们在不同阶段处理不同的业务。

Vue 2 和 Vue 3 选项式 API 的钩子大致是一样的,有以下钩子:

第一次页面加载会触发这四个钩子:

Vue 3 组合式 API 有以下钩子:

nextTick 的使用场景和原理

使用场景

nextTick 是在下次 DOM 更新循环结束之后执行的一个方法。一般在修改数据之后使用这个方法操作更新后的 DOM。

export default {
  data() {
    return {
      message: "Hello Vue!",
    }
  },
  methods: {
    example() {
      // 修改数据
      this.message = "changed"
      // DOM 尚未更新
      this.$nextTick(() => {
        // DOM 现在更新了
        console.log("DOM 现在更新了")
      })
    },
  },
}

原理

在 Vue2 当中,nextTick 可以理解为就是收集异步任务到队列当中并且开启异步任务去执行它们。它可以同时收集组件渲染的任务,以及用户手动放入的任务。组件渲染的任务是由 watcher 的 update 触发,并且将回调函数包装为异步任务,最后推到 nextTick 的队列里,等待执行。

而在 Vue3 当中,nextTick 则是利用 promise 的链式调用,将用户放入的回调放在更新视图之后的 then 里面调用,用户调用多少次 nextTick,就接着多少个 then。

为什么 Vue 组件中的 data 必须是函数?

因为在 Vue 中组件是可以被复用的,组件复用其实就是创建多个 Vue 实例,实例之间共享 prototype.data 属性,当 data 的值引用的是同一个对象时,改变其中一个就会影响其他组件,造成互相污染,而改用函数的形式将数据 return 出去,则每次复用都是崭新的对象。

这里我们举个例子:

function Component() {}

Component.prototype.data = {
  name: "vue",
  language: "javascript",
}

const A = new Component()
const B = new Component()

A.data.language = "typescript"

console.log(A.data) // { name: 'vue', language: 'typescript' }
console.log(B.data) // { name: 'vue', language: 'typescript' }

此时,A 和 B 的 data 都指向了同一个内存地址,language 都变成了 'typescript'。

我们改成函数式的写法,就不会有这样的问题了。

function Component() {
  this.data = this.data()
}

Component.prototype.data = function () {
  return { name: "vue", language: "javascript" }
}

const A = new Component()
const B = new Component()

A.data.language = "typescript"

console.log(A.data) // { name: 'vue', language: 'typescript' }
console.log(B.data) // { name: 'vue', language: 'javascript' }

所以组件的 data 选项必须是一个函数,该函数返回一个独立的拷贝,这样就不会出现数据相互污染的问题。

Vue 组件之间如何通信?

传送门:Vue 3 组件之间如何通信

Vue 项目中做过哪些性能优化?

Vue 和 React 的区别?

传送门:对比其他框架 — Vue.js

Composition API(组合式 API)与 Options API(选项式 API)有什么区别?

v-for 和 v-if 可以同时使用吗?

可以同时使用,但不推荐,具体原因参考官方说明

在 Vue 3 中,当 v-if 和 v-for 同时存在于一个节点上时,v-if 比 v-for 的优先级更高,此时 v-if 无法访问 v-for 中的对象,在 Vue 2 中相反。

当确实需要条件遍历渲染的话,有以下几个方法:

<li v-for="todo in todos.filter(todo => !todo.isDone)">{{ todo.name }}</li>

使用数组的 filter 的方法可以提前对不需要的数据进行过滤,根源上解决这个问题。

<li v-for="todo in todos" v-show="!todo.isDone">{{ todo.name }}</li>

v-show 和 v-if 都可以用于隐藏某个元素,但 v-if 用于决定是否渲染,而 v-show 则使用 display 属性决定是否显示。此时可以避免 v-if 和 v-for 同时使用造成的的渲染问题。

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>

添加额外的标签,根据层级的不同,可以自己决定 v-if 和 v-for 的优先级,这种方法更加灵活也更容易理解,但会有更深的代码结构。

总结

这篇不总结了,就祝大家都能找到一份满意的工作。


参考资料:

上一篇 下一篇

猜你喜欢

热点阅读