Vue 3 常见面试题汇总_vue3面试题

在 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,然后 Vnode 和 oldVnode 进行对比,发现有不一样的地方就直接修改在真实 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 有什么用?

  • 在 Vue 中,key 被用来作为 VNode 的唯一标识。
  • key 主要用在虚拟 DOM Diff 算法,在新旧节点对比时作为识别 VNode 的一个线索。如果新旧节点中提供了 key,能更快速地进行比较及复用。反之,Vue 会尽可能复用相同类型元素。
<ul>
  <li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>

  • 手动改变 key 值,可以强制 DOM 进行重新渲染。
<transition>
  <span :key="text">{{ text }}</span>
</transition>

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 对 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 的钩子大致是一样的,有以下钩子:

  • beforeCreate

实例初始化之前,$el 和 data 都为 undefined

  • created

实例创建完成,data 已经绑定。但 $el 不可用。

  • beforeMount

将 <template> 和 data 生成虚拟 DOM 节点,可以访问到 $el,但还没有渲染到 html 上。

  • mounted

实例挂载完成,渲染到 html 页面中。

  • beforeUpdate

data 更新之前,虚拟 DOM 重新渲染之前。

  • updated

由于 data 更新导致的虚拟 DOM 重新渲染之后。

  • beforeDestroy(Vue 2) | beforeUnmount(Vue 3)

实例销毁之前(实例仍然可用)。

  • destroyed(Vue 2) | beforeUnmount(Vue 3)

实例销毁之后。所有的事件监听器会被移除,所有的子实例也会被销毁,但 DOM 节点依旧存在。该钩子在服务器端渲染期间不被调用。

  • activated

keep-alive 专用,实例被激活时调用。

  • deactivated

keep-alive 专用,实例被移除时调用。

  • errorCaptured

在捕获了后代组件传递的错误时调用。

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

  • beforeCreate
  • created
  • beforeMount
  • mounted

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

  • onBeforeMount()

在组件被挂载之前被调用。

  • onMounted()

在组件挂载完成后执行。

  • onBeforeUpdate()

在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

  • onUpdated()

在组件因为响应式状态变更而更新其 DOM 树之后调用。

  • onBeforeUnmount()

在组件实例被卸载之前调用。

  • onUnmounted()

在组件实例被卸载之后调用。相当于 Vue 2 的 destroyed

  • onErrorCaptured()

在捕获了后代组件传递的错误时调用。

  • onRenderTracked()

当组件渲染过程中追踪到响应式依赖时调用。只在开发环境生效。

  • onRenderTriggered()

当响应式依赖的变更触发了组件渲染时调用。只在开发环境生效。

  • onActivated()

keep-alive 专用,当组件被插入到 DOM 中时调用。

  • onDeactivated()

keep-alive 专用,当组件从 DOM 中被移除时调用。

  • onServerPrefetch()

在组件实例在服务器上被渲染之前调用。只在 SSR 模式下生效。

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 项目中做过哪些性能优化?

  • UI 库按需加载,减小打包体积,以 ElementUI 为例:
// main.js
import { Button, Select } from "element-ui"

Vue.use(Button)
Vue.use(Select)

  • 路由按需加载
// router.js
export default new VueRouter({
  routes: [
    { path: "/", component: () => import("@/components/Home") },
    { path: "/about", component: () => import("@/components/About") },
  ],
})

  • 组件销毁后把同时销毁全局变量和移除事件监听和清除定时器,防止内存泄漏
beforeDestroy() {
  clearInterval(this.timer)
  window.removeEventListener('resize', this.handleResize)
},

  • 合理使用 v-if 和 v-show 使用

Vue 和 React 的区别?

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

  • Options API 会将组件中的同一逻辑相关的代码拆分到不同选项,比如 datapropsmethods 等,而使用 Composition API 较为灵活,开发者可以将同一个逻辑的相关代码放在一起。
  • Composition API 通过 Vue 3.x 新增的 setup 选项进行使用,该选项会在组件创建之前执行,第一个参数 props,第二个参数 context,return 的所有内容都会暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
  • Composition API 上的生命周期钩子与 Options API 基本相同,但需要添加前缀 on,比如 onMountedonUpdated 等。

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

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

总结

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

hods` 等,而使用 Composition API 较为灵活,开发者可以将同一个逻辑的相关代码放在一起。

  • Composition API 通过 Vue 3.x 新增的 setup 选项进行使用,该选项会在组件创建之前执行,第一个参数 props,第二个参数 context,return 的所有内容都会暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
  • Composition API 上的生命周期钩子与 Options API 基本相同,但需要添加前缀 on,比如 onMountedonUpdated 等。

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

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

总结

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值