vue2和vue3面试题

150 阅读5分钟

1.Vue和Element-UI的关系(区别)

  • Vue 是一套用于构建用户界面的渐进式 JavaScript 框架 ,开发者只需要关注视图层, 它不仅易于上手,还便于与第三方库既有项目的整合。是基于MVVM(Model-View-ViewModel 即:视图层-视图模型层-模型层)设计思想。提供MVVM数据双向绑定的库,专注于UI层面。

  • Element-UI 是基于 vue 实现的一套不依赖业务的 UI 组件库

  • Vue 与Element-Ui的关系

    • Element-Ui是基于vue封装的组件库,简化了常用组件的封装,提高了重用性原则;
    • vue是一个渐进式框架,Element-Ui是组件库

2.v-model

  • 在Vue2中,在标签元素上绑定一个v-model,相当于绑定了一个值和一个事件

    • <input v-model="searchText" />
      
    • 等同于
    • <input :value="searchText" @input="searchText = $event.target.value" />
      
    • <template>
          <!-- <input :value="value" @change="$emit('change', $event.target.value)" > -->
          <input :value="searchText" @change="change" >
      </template><script>
          model: {
            prop: 'searchText',
            event: 'change'
          },
          props: {
              searchText: String
          },
          methods:{
              change(event){
                  this.$emit("change",event.target.value)
              }
          }
      </script>
      
  • 在Vue3中,在自定义组件中使用v-model,相当于传递一个 prop 并发出一个事件

    • 默认情况下,使用v-model,相当于传递给组件一个modelValue
    • <ChildComponent v-model="pageTitle" />
      
    • 相当于
    • <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
      
    • 1.将属性的值modelValue绑定
    • 2.触发事件时,发出具有新值的自定义事件@update:modelValue
    • // ChildComponent.vue
      ​
      <template>
          <input :value="modelValue" @input="onChange" /> 
      </template><script setup>
      import { defineProps, defineEmits } from 'vue'const props = defineProps({
        modelValue: {
          type: XXX,
          required: true
        }
      })
      //若需要修改默认的modelValue名称,则需要将modelValue为修改值即可
      const emits = defineEmits(['update:modelValue'])
      ​
      const onChange = () => {
          emits('update:modelValue', 新值)
      }
      </script>
      
  • 多个v-model绑定

    • 通过v-model参数的使用,可以在一个组件绑定多个v-model
    • <UserName v-model:first-name="first" v-model:last-name="last" />
      
    • //UserName组件
      <script setup>
      import { defineProps, defineEmits } from 'vue'const props = defineProps({
        first-name: {
          type: String,
          required: true
        },
        last-name: {
          type: String,
          required: true
        },
      })
      const emits = defineEmits(['update:first-name','update:last-name'])
      </script>
      

3.watch 和 watchEffect 的区别是什么

  • 两者都可监听 data 属性变化

  • watch

    • watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数
    • watch 需要明确监听哪个属性 它是惰性执行副作用,它不会立即执行,但可以配置 immediate,使其主动触发
    • watch可以同时获取更改前和更改后的值
  • watchEffect

    • watchEffect只需要传递一个回调函数,不需要传递侦听的数据,它会在页面加载时主动执行一次
    • watchEffect 接收一个函数,会根据其中的属性,自动监听其变化(依赖变化就会重新执行函数)
    • watchEffect获取不到更改前的值

4.如何理解 ref、toRef 和 toRefs

  • ref

    • 生成值类型的响应式数据
    • 可用于模板和 reactive
    • 通过.value 修改值
  • reactive

    • 用 reactive 做对象的响应式
  • toRef

    • 针对一个响应式对象(reactive 封装)的 prop
    • 创建一个 ref,具有响应式 toRef(state,....)
    • 两者保持引用关系
  • toRefs

    • 将响应式对象(reactive 封装)转换为普通对象 toRefs(state)

5.vue2和vue3双向绑定数据的原理

  • Vue2使用Object.defineProperty()方法

    • 使用Object.defineProperty()方法为每个属性添加getter和setter,从而实现双向绑定
  • Vue3使用Proxy对象来实现双向绑定

    • 使用Proxy对象来包装原始对象,并重写getter和setter方法,从而实现双向绑定

6.vue2和vue3的区别

  • ref和reactive
  • 生命周期

    大部分生命周期钩子名称上 + “on”,功能上是类似的

  • 多根节点

  • Composition API

  • 异步组件

    Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

  • Teleport

    Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗

  • 虚拟DOM

    Vue3 相比于 Vue2,虚拟DOM上增加 patchFlag 字段

    调用createElementVNode时,第四个参数即 patchFlag 字段类型

    1代表节点为动态文本节点,那在 diff 过程中,只需比对文本对容,无需关注 class、style等

    1代表所有的静态节点,都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建

  • 打包优化

    vue3移除 JavaScript 上下文中未引用的代码,主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用

7.Vue3中v-for 中使用 ref(通过:ref="(el) => setItemRefs(el, item)"的写法)

  • 基本使用
 <template>
   <div ref="hello">小猪课堂</div>
   <ul>
     <li v-for="item in 10" ref="itemRefs">
       {{item}} - 小猪课堂
     </li>
   </ul>
 </template>
 <script setup lang="ts">
 import { onMounted, ref } from "vue";
 
 
 const itemRefs = ref<any>([]);
 onMounted(() => {
   console.log(itemRefs.value);
 });
 </script>
  • ref绑定函数
//该函数会默认接收一个 el 参数,这个参数就是我们需要获取的 div 元素。假如需求中我们采用这种方式的话,那么完全可以把 el 保存到一个变量中去,供后面使用。
<template> 
  <div :ref="setHelloRef">小猪课堂</div>
</template>
<script setup lang="ts">
import { ComponentPublicInstance, HTMLAttributes } from "vue";
​
​
const setHelloRef = (el: HTMLElement | ComponentPublicInstance | HTMLAttributes) => {
  console.log(el); // <div>小猪课堂</div>
};
​
//上面的代码可以改为
<template>
  <ul>
    <li v-for="item in 10" :ref="(el) => setItemRefs(el, item)">
      {{ item }} - 小猪课堂
    </li>
  </ul>
</template>
<script setup lang="ts">
import { ComponentPublicInstance, HTMLAttributes, onMounted } from "vue";
let itemRefs: Array<any> = [];
const setItemRefs = (el: HTMLElement | ComponentPublicInstance | HTMLAttributes, item:number) => {
  if(el) {
    itemRefs.push({
      id: item,
      el,
    });
  }
}
onMounted(() => {
  console.log(itemRefs);
});
</script></script>

8.父组件调用子组件的方法或变量(通过defineExpose)

//子组件
<template>
  <div>{{ message }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
​
​
const message = ref<string>("我是子组件");
const onChange = () => {
  console.log("我是子组件方法")
};
defineExpose({
  message,
  onChange
});
</script>
    
//父组件
    <script>
        <template>
          <child ref="childRef"></child>
        </template>
        <script setup lang="ts">
        import { onMounted, ref } from "vue";
        import child from "./child.vue";
        const childRef = ref<any>(null);
        onMounted(() => {
          console.log(childRef.value); // child 组件实例
          console.log(childRef.value.message); // 我是子组件方法
        });
    </script>

9.自定义指令的钩子函数(生命周期)

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

10.Vue中$set用法

  • 原因:Vue 无法检测 对象property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
  • 简说(由于Vue无法检测对象和数组的变化,因为Vue会在初始化实例阶段就对data中所定义的数据进行响应式处理,所以Vue提供了Vue.set(object, propertyName, value)
  • Vue.set可以写成this.$set()

11.Vue中$nextTick用法

  • 与$set第三点类似
  • 为了在数据变化之后等待 Vue 完成更新 DOM

12.Vue3中setup语法糖

  • 它是一个函数(接收 propscontextsoltemit)参数),会暴露所有的内容给组件的其余部分以及组件的模板
  • 在script标签中添加setup后,组件只需引入不用注册,属性和方法也不用return返回,也不用写setup函数,也不用写export default ,甚至是自定义指令也可以在我们的template中自动获得
  • setup语法糖中的defineProps:子组件接收父组件中传来的props
  • setup语法糖中的defineEmits:子组件调用父组件中的方法
  • setup语法糖中的defineExpose:子组件暴露属性,可以在父组件中拿到

13.vue常用组件通信方式及vue2和vue3写法对比

  • props/$emit

    • vue2写法
      • 父传子:在父组件上定义自定义属性,子组件利用props接收
      • 子传父:在子组件中通过this.$emit("自定义方法名",新值)触发自定义事件,父组件中通过@接收
    • vue3写法
      • 父传子:在父组件上定义自定义属性,子组件利用defineProps接收
      • 子传父:在子组件中通过defineEmits(["自定义方法名"])触发自定义事件,父组件中通过@接收
  • ref/$parent

    • vue2写法

      • 子传父:通过ref属性,在父组件中ref标记组件,通过this.$refs.child方式获取属性值和方法
      • 父传子:使用parent(访问的是上一级父组件的属性和方法),在子组件中通过this.parent(访问的是上一级父组件的属性和方法),在子组件中通过this.parent方式获取
    • vue3写法

      • 子传父:通过ref父组件获取子组件的属性或者调用子组件方法,前提需要子组件提前暴露出属性值或者方法,使用defineExpose
      • 父传子:通过parent在子组件中获取父组件的数据和方法,前提需要父组件提前暴露出属性值或者方法,使用defineExpose,子组件通过parent在子组件中获取父组件的数据和方法,前提需要父组件提前暴露出属性值或者方法,使用**defineExpose**,子组件通过parent获取
  • provide/inject

    • vue2写法

      • // 父组件
        export default{
            provide(){
                return {
                    msg: this.msg 
                }
            }
        }
        ​
        // 后代组件
        export default{
            inject:["msg"]
        }
        
      • 注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变
    • vue3写法

      • // Parent.vue
        <script setup>
            import { provide } from "vue"
            provide("name", "小心")
        </script>
        ​
        // Child.vue
        <script setup>
            import { inject } from "vue"
            const name = inject("name")
            console.log(name) //小心
        </script>
        
  • eventBus事件总线

    • vue2写法

      • 通过Vue.prototype.$bus = new Vue()穿件事件总线
      • 通过emit触发自定义事件,this.bus.bus.emit
      • 通过on监听事件,this.bus.bus.on
    • vue3写法

      • 借助mitt插件来实现代替,原理还是 EventBus
  • Vuex/Pinia

    • vuex核心概念:state、mutations、actions、getters、modules
    • pinia核心概念:state、actions、getters。没有mutation、modules

14.react和vue的区别

  • 核心思想不同:React推崇函数式编程(纯组件),数据不可变以及单向数据流,Vue则灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确

  • 组件写法差异:React推荐的做法是JSX,也就是把 HTML 和 CSS 全都写进 JavaScript 中,Vue 推荐的做法是 template 的单文件组件格式,即 html,css,JS 写在同一个文件

  • diff算法不同:

    • 总结:

      • 传统Diff算法是循环递归每一个节点,算法复杂度为O(n*3)
      • 采用虚拟DOM,算法复杂度为O(n)
      • 不同的组件产生不同的DOM结构,type不同,就会直接删除旧DOM,创建新的DOM
      • 同一层次的一组子节点,可以通过唯一的 key 区分
    • React的Diff算法核心实现

      • react首先对新集合进行遍历
      • 通过唯一key来判断老集合中是否存在相同的节点。如果没有的话创建,
      • 如果有的话,会将节点在新集合中的位置和在老集合中lastIndex进行比较
      • 如果渲染的节点位置小于老位置,进行移动操作,否则不进行移动操作
      • 如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作
    • Vue的Diff算法核心实现

      • 旧children新children各有两个头尾的变量StartIdxEndIdx,它们的2个变量相互比较,一共有4种比较方式
      • 如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明旧children新children至少有一个已经遍历完了,就会结束比较
  • 响应式原理不同:

    • Vue

      • Vue依赖收集,自动优化,数据可变,
      • Vue递归监听data的所有属性,直接修改
      • 当数据改变时,自动找到引用组件重新渲染
    • React

      • React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state,数据改变时,React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制

15.vue中v-for的key作用是什么

  • vue中列表循环需要加:key='唯一标识',唯一标识尽量是id,目的是为了高效地更新虚拟DOM
  • key主要用于dom diff算法,diff算法为同级比较,比较当前标签上的key还有他当前的标签名,如果key和标签名都一样时只移动,不会重新创建元素和删除元素
  • 尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样