常问的一些Vue面试题总结

262 阅读9分钟

说说虚拟DOM

虚拟DOM就是用来js对象来描述真实DOM的,用来最小化找出新旧DOM的差异,以及减少对比性能,比如我们使用Vue编程是声明式的,其实这也是Vue底层用命令式的代码帮我们封装好了,我们可以直接用声明式来编码,其实用虚拟DOM的更新技术理论上不可能比原生JavaScript操作DOM更快的。

父子组件的渲染顺序(vue2)

  • 同步组件

    • 加载阶段
    父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
    
    • 更新阶段
    父beforeUpdate->子beforeUpdate->子updated->父updated
    
    • 销毁阶段
    父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
    
  • 异步组件 即通过 ()=> import('...')方式引入组件

    • 加载阶段
    父beforeCreate->父created->父beforeMount->父mounted->子beforeCreate->子created->子beforeMount->子mounted
    
    • 更新阶段
    父beforeUpdate->子beforeUpdate->子updated->父updated
    
    • 销毁阶段
    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed 
    

v-if和v-show的使用场景

v-if:是通过动态删除和创建整个DOM元素的,相对而言更消耗性能,一般适用于非频繁切换显示/隐藏

v-show:是通过css的display:none来隐藏整个dom元素的,适用于频繁切换的应用场景

v-for和v-if的优先级

通过源码可以知道,当v-for和v-if同时使用在一个标签上,在vue2里v-for的优先级是比v-if高的,vue3中v-if优先级更高。

对vuex的理解

vuex是用于状态集中管理数据共享的,比如一些用户信息、token一般可以存储在vuex和localStorage,搭配使用。

五大核心属性

  • state 存储数据
  • mutations 唯一可以直接修改state里面的数据的地方 不可以执行异步代码
  • actions 可以执行异步代码,用于派发mutations来修改数据,可以在这里发请求
  • getters 可以简化我们取vuex里面的数据,比如计算购物车的总价,类似于计算属性
  • modules 当我们的项目很大的时候,vuex里面的数据多了,就很难去阅读了,可以分模块,比如用户信息模块 新闻列表模块等

vue中的路由导航守卫

  • 全局前置守卫

可以使用router.beforeEach注册一个全局前置守卫:

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

当一个导航被触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve完之前一直处于等待中

每个守卫方法接受两个参数:

  • to:即将要进入的目标 用一种标准化的方式
  • from:当前导航正要离开的路由 用一种标准化的方式
  • next:确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到/login错误用例

可以返回的值如下:

  • false:取消当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到from路由对应的地址。

  • 一个路由地址:通过一个路由地址跳转到一个不同的地址,就像调用router.push()一样,你可以设置诸如repace:truename:'home'之类的配置。当前的导航被中断,然后进行一个新的导航,就和from一样。

  • 全局解析守卫

可以用router.beforeResolve注册一个全局守卫。这和router.beforeEach类似,因为它在每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

  • 全局后置钩子

路由页面跳转成功之后执行的一个钩子,这里一般可以用于修改页面标题,声明页面等事情。

  • 路由独享守卫
const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

beforeEnter 守卫 只在进入路由时触发,不会在 paramsqueryhash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。

  • 组件内守卫

可以在路由组件内直接定义路由导航守卫(传递给路由配置的)

可配置API

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from,next) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
    next(vm=>{
      console.log(vm)  // vm就可以拿到当前组件实例对象
    })
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

keep-alive的使用

<keep-alive includes="['Son']">  
  // includes 是要指定哪些组件需要缓存,可以写成数组/正则/字符串,与组件里的name配对
  // excludes 指定哪些组件不需要缓存  .....
  // 需要缓存的组件
  <router-view />
</keep-alive>

有没有自定义过一些指令

  • 一键copy文字
  • 图片加载不出来,显示默认图片
  • 表单自动获取焦点
  • 图片加水印

data、props、methods、watch、computed的优先级

props > methods > data > computed > watch

computed与watch的应用场景

computed:当多个数据依赖一个数据的时候,用computed,具有缓存性,必须有返回值,因为是依赖计算出来的这个值。

watch:当一个数据依赖多个数据的时候,watch里面可以执行异步代码,computed不行(比如商品的详情页,我们可以监听商品的ID的变化,去发请求拿数据)。

生命周期

vue2:

beforeCreate=>created

创建前创建完毕,

beforeCreate此时还没有el和data和methods,数据代理还没开始

created此时已经有data和methods了,数据代理也开始了

beforeMount>mounted

挂载前挂载完毕

beforeMount可以发送ajax请求,开启定时器、发布订阅等操作了

mounted 此时已经有DOM结构了,可以操作dom,但是不建议这么做

beforeUpdata==>updated

数据更新会触发这两个钩子,不能在updated这个钩子函数里面更新数据,这样会一直触发这个函数,会形成死循环。

beforeDestroy==>destroyed

beforeDestroy一般在这个钩子上,做一些收尾工作,比如==清除定时器==、==取消订阅==、==清除自定义事件==等.

vue3:

beforeDestroy 改为 beforeUnmount

destroyed 改为 unmounted

vue2中当没有指定el要挂载的元素,beforeCreate和created都会触发

vue3不会

对vue3有了解过吗

vue3中响应式用到了ES6中的 Proxy 代理对象,但是对于基本数据类型,还是用了Object.defineProperty来做响应式。

vue3中打包速度和更好支持TS,还有组合式API,数据和方法都整合到了setup函数里面setup执行时机比beforeCreate还快,setup里面的this -undefined,比传统vue2的optionsApi能够更好更优雅的管理代码,可以把每个功能模块拆成一个hooksvue3里称为组合式api模块,需要就引入这个模块。

vue3中的Proxy性能更好,因为proxy侦听的对象本身,而defineProerty是遍历对象的每个属性,在性能消耗方面,vue3性能消耗更低。

由于Proxy,vue3能侦听到数组被修改,而vue2不能,但能通过那其中方法去改变(改变原数组的方法),都知道,vue2中给对象后新增的属性是丢失了响应式的除非使用$setAPI,而vue3没有,后新增的属性也是响应式的

新增了watchEffect API,它是一个函数,接受一个回调函数,它是非惰性的。在这个函数里面用到了某个数据,当你读取或修改了这个数据,就会被这个函数侦听到,从而触发这个回调函数,就可以在这个会回调里面写一些自定义逻辑了

移除了过滤器:因为像过滤器能实现的,用computed和methods也能实现,用过滤器,反而还有学习成本。

用到的内置api要单独按需引入

// 例如:
import {ref,reactive,watchEffect} from 'vue'

更好的浏览器红利

vue3中做过哪些性能优化

reatvie与shallowReactive的区分使用场景,当一个数据结构是深层次的,但要做响应式的只有第一层的话,可以用shallowReactive

import { shallowReactive } from 'vue'
exprot default {
  name:'xxx',
  setup(){
    const data = shallowReactive({
      name:'杨某某',
      age:18
    })
    return {data}
  }
}

vue中nextTick的作用与原理

vue更新页面是采用了异步更新的,如果我们想要获取到最新的DOM,可以用vue给我们提供的nextTick,在它的回调里面可以拿到更新后的DOM元素,但是vue是不推荐我们直接操作DOM的,nextTick底层用了promise.then方法,如果浏览器不支持,就会使用mutationObserversetTimeout

  • nextTick的细节:如果你没有提供回调,它会返回一个promise对象,所以可以使用asyncawait
// 例如
  nextTick(()=>{
    // 做一些操作
  })
// 等效于
   await nextTick()
    // 做一些操作
  • 原理:

    vue更新数据是异步更新的,当发现数据变更,会开启一个队列,推进这个队列,当多次数据变更只会推进一次,这样会减少一些不必要的更新和计算。

Vue.use原理

概念:

如果插件是一个对象,必须提供 install 方法

如果插件是一个函数,它会被作为 install 方法,install方法调用时,会将Vue构造函数作为参数传入

注意:该方法需要在调用new Vue() 之前 被调用

vue中的token持久化存储

vue中存储token,一般像token这样的数据都要做持久化的,我们都知道存储在本地localStorage里面的数据只有在用户主动去清除才会被清除的,是持久的,但是还有一个问题,存储在localStorage里的数据不是响应式的,我们可以也存一份到vuex里,我们可以借助一个插件-- persistedstate 来做token持久化。

1、安装
npm i persistedstate
2、导入插件
import persistedstate from 'vuex-persistedstate'
3、使用
modules: {},
plugins: [
  // 使用vuex数据持久化插件
  persistedstate({
    paths: ['tokenObj']
  })
]

vue多组件嵌套通信方式

  • eventBus 事件总线 适用于任意组件间通信

  • 逐层传递props

  • 消息订阅与发布 需要安装插件

  • vue2.x后新增的provideinject

发送方:

import { provide } from 'vue'
provide('事件名',要传递的数据)

接收方:

import { inject } from 'vue'
const res = inject('事件名')   // res 得到的就是数据

为什么vue中methods对象this能到data里面的数据---原理

通过劫持,然后bind修改this

function initMethods (vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) {
      {
        if (typeof methods[key] !== 'function') {
          warn(
            "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
            "Did you reference the function correctly?",
            vm
          );
        }
        if (props && hasOwn(props, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a prop."),
            vm
          );
        }
        if ((key in vm) && isReserved(key)) {
          warn(
            "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
            "Avoid defining component methods that start with _ or $."
          );
        }
      }
      / bind绑定函数的的this指向vue构造函数 /
      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    }
}

vue3的patch打补丁做了些什么

  • 对比新旧vNode的tag标签有没有改变,没有再对比的props有没有改变
// oldVNode 旧节点 newVnode  新节点
function ptach(oldVNode,newVnode){
if(oldVNode.tag === newVnode.tag){
 // 存储旧节点
  const el = oldVNode.el
  // props
  const oldProps = oldVNode.props || {}
  const newProps = newVNode.props || {}
  // 遍历新对象
  for(const key in newProps){
    const oldValue = oldProps[key]
    const newValue = newProps[key]
    // 当新旧的props发生改变了,把最新的props赋值给旧的节点
    if(newVlaue !== oldValue){
      el.setAttribute(key,newValue)
    }
  }
  // 遍历旧对象
  for(const key in oldProps){
    // 如果旧对象没有key
    if(!(key in newProps)){
      el.removeAttribute(key)
    }
  }
  // children
  const oldChildren = oldVNode.children
  const newChildren = newVNode.children
  if(typeof newChildren = 'string'){
  }
}
}

vue3的细节

  • compunted返回的是一个ref对象
  • watch不能直接侦听一个字符串/数字,需要侦听的是ref对象或者第一个参数传递一个函数,函数的返回值是你需要侦听的目标

函数式组件

  • 优点:
    • 函数式组件 不会有状态,不会有 响应式数据 ,也没有自己的 生命周期钩子 这些东西,所以性能比普通组件性能要好。
    • 函数式组件 和普通的对象类型的组件不同,它不会被看作成一个真正的组件,我们知道在 patch 过程中,如果遇到一个节点是组件的 vnode ,会递归执行子组件的初始化过程;而函数式组件的 render 生成的是普通的 vnode ,不会又递归的子组件的过程,因此渲染开销会低很多。

computed的细节

computed:{
  a(){
    return 100
  },
  b({a}){   // 入参可以拿到当前组件实例对象
    return a+100
  }
}

可以减少引用this,这样就不会在组件刷新时重新获取getter了,这也是性能优化的一种手段。