[面试] - Vue常见面试题

186 阅读8分钟

前言

记录一些个人认为比较常见的Vue相关的常见面试题以及个人理解答案,如有错误请见谅以及指出,谢谢

1.1、Vue生命周期

Vue的生命周期指的是Vue实例从创建到销毁的过程 -- 开始创建、初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载。 而包含的生命钩子函数有:

  • beforCreate: 实例创建前,未有data,event,watcher
  • created: 实例创建完成,已有data和方法的运算,watch/event的事件回调,未有$el
  • beforMount: 挂载开始前,相关render函数首次被调用
  • mounted: 挂载完成,此时有$el
  • beforupdate: 数据更新时调用,虚拟DOM打补丁之前
  • updated: 虚拟DOM重新渲染和打补丁后调用
  • beforDestroy: 实例销毁前
  • destroy: 实例销毁后
  • activated: keep-alive组件激活时
  • deactivated: keep-alive组件停用时
  • errorCaptured: 捕获一个来自子孙组件的错误时调用,此钩子有3个参数,错误对象、发送错误的组件实例、包含错误来源信息的字符串

1.2、如何理解Vue是一套渐进式的框架

渐进式代表的含义是: 没有多做职责以外的事,只提供了核心的 组件系统双向数据绑定

1.3、Vue的两个核心

  • 数据驱动: 双向数据绑定,vue2利用的是es5的Object.definedProperty和存储器属性:getter和setter(所以只兼容ie9及以上),其主要核心是VM,也就是viewModel,保证数据与视图的一致性
  • 组件系统: 组件系统的核心选项
    • 1、模板(template): 模板声明了数据和最终展现给用户的DOM之间的映射关系
    • 2、初始数据(data):一个组件的初始数据状态,对于可复用的组件来说,这通常是私有的状态
    • 3、接收的外部参数(props): 组件之间通过参数来进行数据的传递和共享
    • 4、方法(methods): 对数据的改动操作一般都在组件的方法内进行
    • 5、生命周期钩子函数:一个组件会触发多个生命钩子函数
    • 6、私有资源(assets):Vue当中将用户自定义的指令、过滤器、组件等统称为资源,一个组件可以声明自己的私有资源,使其只允许该组件和他的子组件可以调用

1.4、Vue常用指令

v-ifv-showv-forv-bindv-onv-modelv-htmlv-textv-oncev-pre:跳过这个元素和它的子元素的编译过程,一些静态内容不需要编辑加这个指令可以加快编译

1.5、v-if 和 v-show

  • 相同点:都可以动态显示DOM元素
  • 不同点:
    • 1、v-if是真正的条件渲染,会将元素摧毁和重建,而v-show只是简单的切换css的display属性
    • 2、v-if是惰性的,如果在初始渲染条件时为假,则不处理,直到条件第一次为真时,才会开始渲染模块,而v-show不管初始化条件是什么,元素都会渲染,然后根据条件设置不同的display属性
    • 3、v-if有更高的切换消耗,而v-show有更高的初始化渲染消耗
    • 4、v-if适合条件改变次数少的场景,v-show适合频繁切换条件的场景

1.6、v-html 和 v-text

v-html是更新元素的innerHTML,而v-text是更新元素的textContent

1.7、Vue常用修饰符

v-on常用修饰符

  • .stop: 调用event.stopPropagation()禁止事件冒泡
  • .once: 只触发一次回调
  • .left: 鼠标左键触发
  • .right: 鼠标右键触发
  • .middle: 鼠标中键触发
  • .native: 监听组件根元素的原生事件
  • .prevent: 调用event.preventDefault()阻止事件默认行为

v-bind常用修饰符

  • .prop: 被用于绑定DOM属性(property)
  • .camel: 将kebab-case特性名转为cameCase
  • .sync: 语法糖,会拓展成一个更新父组件绑定值的v-on侦听器

v-model常用修饰符

  • .lazy: 取代input监听change事件
  • .number: 输入的字符串转为数字
  • .trim: 输入首尾空格过滤

1.8、Vue中key的作用

key的特殊属性主要作用在Vue的虚拟DOM的算法中,在新旧nodes对比时辨识VNodes。

如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法

使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素

1.9、Vue事件中如何使用event对象

<a href="javascript:;" data-id="1" @click="clickEvent($event)" ></a>

clickEvent(event){ 
  // 获取data-id
  console.log(event.target.dataset.id)
  // 阻止事件冒泡
  event.stopPropagation()
}

1.10、$nextTick的作用 和原理

  • 作用:因为Vue的异步更新队列,$nextTick是用来知道什么时候DOM更新完成的
  • 原理: 使用了宏任务和微任务,定义了一个异步方法,多次调用nexttick会将方法存入队列中,通过这个异步方法清空队列

1.11、异步更新队列

Vue在观察到数据变化时并不会直接更新DOM,而是开启一个队列,并缓存在同一事件循环中发生的所有数据变更。
所以在同一个watcher多次触发,只会添加到队列一次,因此可以去除重复数据可以避免不必要的计算的DOM操作。
Vue在内部对异步队列尝试使用原生的 Promise.thenMatationBoservesetImmediate,如都不支持,则采用 setTimeout

1.12、data为什么是个函数

因为一个组件可以是共享的,但他们的data应该是私有的,所以每个组件都要return一个新的data对象,返回一个唯一对象,不和其他组件共用。
这个是因为js自身的特性(原型链)带来的,与vue自身设计无关

1.13、v-if和v-for的优先级

  • vue2.x上,v-for优先级比v-if高,vue3.x上,v-if优先级比v-for高
  • 所以在vue2.x同一节点上使用v-if和v-for,会每个循环都执行if,所以写法上用if包裹for

1.14、Vue组件的通信方式

  • props和$emit: 父子组件通信
  • $parent和$children: 通过获取父子组件的实例来实现通信
  • $attrs和$listeners: 适用于嵌套深的组件通信,
    attrs会包含父组件中没有被props接收的所有属性(不包括classstyle),可以通过vbind="attrs会包含父组件中没有被props接收的所有属性(不包括class和style),可以通过v-bind="attrs"直接将这些属性传入内部组件。
    listeners会包含父组件中所有的von事件监听器(不包括.native修饰器的),可以通过von="listeners会包含父组件中所有的v-on事件监听器(不包括.native修饰器的),可以通过v-on="listeners"传入内部组件
    // 父组件
    <template>  <Child :name="name" @setName="setName" />  </template>
    // 子组件 child
    <Child2 v-bind="$attrs" v-on="$listenters" />
    // 孙组件 child2
    <template><div>{{name}}</div></template>
    export default{
      created(){
        this.$emit('setName', 'test')
      }
    }
    
  • provide和inject: provide 和 inject需要一起使用,可以使一个祖先组件向所有子孙组件注入一个依赖,可以指定想要提供给后代组件的数据和方法
// 祖先组件
export default {
 provide: { name: 'test' }
}
// 子孙组件
export default {
 inject: ['name'],
 created(){
   console.log(this.name)
 }
}
  • eventBus: 在相互需要通信的两个组件中,引用同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信
  • vuex和浏览器缓存等

1.15、keep-alive组件

  • keep-alive主要作用于保留组件状态和避免重新渲染
  • keep-alive属性有 include、exclude(可使用自字符串和正则,exclude优先级大于includes),max: 可以存储多少个组件实例
  • 生命周期包含 activated - 组件激活时调用,和 deactivated - 组件失活时调用,服务器端渲染期间不会被调用

1.16、数组可以触发视图更新的方法

push,pop,shift,unshift,splice,sort,reverse ……

1.17、data变更但视图没有更新的原因

  • Vue无法检测创建时不存在data中的属性
  • Vue无法检测对象属性的添加和删除
  • Vue不能检测数组的变化,也就是不能根据数组索引直接修改一个数组项
  • Vue不能检测直接修改数组长度的变化

1.18、计算属性computed和方法methods调用有什么区别

  • 计算属性必须返回结果
  • 计算属性是基于它的依赖缓存的,一个计算属性所依赖的数据发生变化时,它才会重新取值
  • 使用计算属性还是方法methods,取决于是否需要缓存,当遍历大数据和做大量计算时,应当使用计算属性
  • 计算属性是根据依赖自动执行的,methods需要事件调用

1.19、自定义指令

Vue.directive("test",{
  inserted: function(el){
    
  }
})
<input v-test />

1.20、自定义指令的钩子函数

  • bind - 只调用一次,指令第一次绑定到元素时调用
  • inserted - 被绑定元素插入父节点时调用
  • update - 所在组件的VNode更新时调用
  • componentUpdated - 指令所在组件的VNode及其子VNode全部更新后调用
  • unbind - 只调用一次,指令与元素解绑时调用

1.21、computed和watch区别

计算属性computed

  • 支持缓存,只有依赖数据发生变化,才会重新进行计算
  • 不支持异步,当computed内有异步操作时无效,无法监听数据变化
  • computed属性值默认走缓存,基于data中声明过或者父组件传递的props中数据通过计算得到的值
  • 可以监听多个依赖数据变化

侦听属性watch

  • 不支持缓存,数据改变则直接会触发操作
  • watch支持异步
  • 监听的函数接受两个参数,newValue,oldValue
  • 只能监听一个数据
  • watch拓展参数
    hander: 回调函数
    immediate:组件初始化加载后立即触发
    deep:深度监听,发现对象内部值的变化

1.22、Object.defineProperty和proxy

区别

  • Object.definedProperty只能劫持对象的属性,需要遍历对象的每个属性,如果属性值也是对象,则深度遍历,而proxy是直接代理对象,不需要遍历操作
  • Obejct.definedProperty对新增属性需要手动进行observe,因为劫持的是对象的属性,所以新增属性时需要重新遍历对象,再对新增的属性再使用definedProperty进行劫持,而proxy可以直接监听对象属性的添加

definedProperty缺点

  • 不能监听数组变化
  • 必须遍历对象的每个属性
  • 必须深层遍历嵌套对象

proxy对比definedProperty优点

  • proxy可以监听整个对象而不是对象某个属性
  • 可以监听数组的变化
  • proxy结果返回一个新的对象,可以直接操作新对象,而不是像definedProperty遍历属性进行修改
  • 性能相对更好,但浏览器兼容性不够

1.23、diff算法

在数据发生变化时,Vue是先根据真实DOM生成一个虚拟DOM树,当虚拟DOM某个节点的数据改变后会生成一个新的VNode,然后新的VNode会跟旧的VNode作比较,发现不一样的地方会直接修改在真实的DOM上,实现更新节点
> 流程简述
- 1、先同级比较,然后再比较子级节点
- 2、先判断一方有子节点一方没有子节点的情况
- 3、比较都有子节点的情况
- 4、递归比较子节点

1.24、slot插槽

具名插槽 有多个插槽时,需要指定名字去区分插槽,参数为name,不填默认default,没有被template包裹的都是默认插槽

// base-layout组件
<div>
   <header>
     <slot name="header"></slot>
   </header>
   <main>
     <slot></slot>
   </main>
</div>
// 父组件
<base-layout>
 <template v-slot:header>我是header</template>
 <p>我是main1</p>
 <p>我是main2</p>
</base-layout>

作用域插槽 可以访问组件内部数据的可复用插槽,也可以理解为带数据的插槽,在slot上绑定数据,传递给父组件

// child组件
<div>
 <slot v-bind:user="user"></slot>
</div>
data(){
 return {
   user: { name: 'test' }
 }
}
// 父组件
<child>
 <template v-slot:default="slotProps">{{slotProps.user.name}}</template>
</child>

1.25、MVVM和MVC

MVC

  • Model(模型) - 负责从数据库中获取数据
  • View(视图) - 负责展示数据的地方
  • Controller(控制器) - 用户交互的地方,例如点击事件 思想: Controller将Model的数据展示在View上

MVVM

  • Model(模型) - 负责从数据库中获取数据
  • View(视图) - 负责展示数据的地方
  • VM - ViewModel,利用数据绑定和DOM事件监听实现数据的双向绑定 思想:实现View和Model的自动同步,也就是Model的属性改变时,不需要手动操作显示DOM元素去改变view的显示

1.26、SSR

  • SSR是服务器渲染
  • 基于node.js serve服务环境开发,所有html代码在服务器渲染
  • 数据返回给前端,然后前端把返回的数据转为浏览器识别的html代码
  • SSR首次加载更快,有利于SEO优化

1.27、props如何自定义验证

  props: {
    num: {
      type: Number,
      default: 1,
      validator: ()=> value <= 2 // 返回false则验证不通过
    }
  }

1.28、scoped

给style标签添加scoped,可以给元素添加data-v-xxxx标记,确保各个文件的css互不影响

1.29、相同路由组件如何重新渲染

Vue默认不会对相同的路由切换做重新渲染,可以添加key做处理

const router = [
 {
   path: '/a',
   component: MyComponent
 },
 {
   path: '/b',
   component: MyComponent
 }
]
<template><router-view :key="$route.path"></router-view></template>

2.1、 Vue-Router 有哪些导航钩子

  • 1、全局守卫 router.beforEach(to, from, next)
  • 2、全局解析守卫 router.beforResolve(to, from, next)
  • 3、全局后置守卫 router.afterEach(to, from)
  • 4、路由独享守卫 beforEnter(to, from, next)
  • 5、组件内守卫
    • beforRouteEnter(to, from, next)
    • beforRouteUpdate(to, from, next)
    • beforRouteLeave(to, from, next)

2.2、Vue-router 路由跳转方式

  • $router.push 向history栈添加一个新的记录
  • $router.replace 替换掉当前history,不会再history添加新纪录
  • $router.go(n) 在history记录向前或向后多少步

2.3、Vue-router 路由传参方式

Vue-router提供了 paramsquerymeta 三种页面传参方式

query和params区别

  • params是路由的一部分,所以传参需要在路由后面添加参数 /index/:id/:name,如果路由没有配置参数,页面刷新后参数会消失
  • params方式只能用name来引入路由,而query使用path和name都可以
  • params 方式不会再地址栏显示参数,query会在地址栏显示参数
// 不带参数
this.$router.push('index')
this.$router.push({path: 'index'})
this.$router.push({name: 'index'})
// params 必须和name搭配使用
this.$router.push({name: 'index', params: {id: 1})
// query
this.$router.push(name: 'index', query: {id: 1})
// meta 路由元信息
export default new Router({
  routers: [
    {
      path: '/index',
      name: 'index',
      component: Index,
      meta: {id: 1}
    }
  ]
})

2.4 Vue-router的routerrouter和route

  • $route: 是路由信息对象,包括path、params、hash、query、fullPath、metched、name等路由信息参数
  • $router: 是路由实例,包括路由的跳转方法,钩子函数等

2.5、Vue-router路由懒加载

结合Vue异步组件Webpack代码分割功能实现路由懒加载

  • 1、定义一个能被webpack自动代码分割的异步组件
const Index = () => import('/index')
  • 2、添加路由配置
 const router = new VueRouter({
  routers: [
    { path: '/index', component: Index }
  ]
 })
  • 3、在build/webpack.base.conf.js的output属性,添加chunkFilename
output: {
  path: config.build.assetsRoot,
  filename: '[name].js',
  chunkFilename: '[name].js',
  publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
}

2.6、Vue-router的hitstory和hash

hash 模式

地址栏URL中的#符号,就是hash符号,叫哈希符,hash不会被包括在http请求中,对后端完全没有影响,因此改变hash不会重新加载页面
hash模式利用的是window.onhashchange事件,哈希值变化时会自动调用hashchange的监听事件,从而得到改变后的url,加载对应页面

history 模式

history模式是利用HTML5的 pushStatereplaceState 方法,用来完成URL跳转而无需重新加载页面,但需要服务器配置支持,否则刷新后不会停留在当前路由

pushState 和 replaceState只会导致浏览器history对象发生变化,从而改变当前地址栏的URL,但浏览器不会向后端发送请求,也不会触发popState事件的执行

3.1、Vuex 是什么

vuex是一个专门为vue开发的状态管理器,采用集中式存储管理应用的所有组件的状态
vuex应用的核心是store仓库

3.2、vuex有哪些属性

- state: vuex的基本数据,用来存储数据
- getter: 从state派生的数据,相当于state的计算属性
- mutation: 提交更新数据的方法,同步的, fn(state,data)
- action: 提交更新数据的方法,可以包含异步才做,提交的是mutation,而不是直接跟新state, fn({commit}, data)
- modules: 模块化vuex
- Vue里调用action用dispatch,mutation用commit

3.3、action和mutation的区别

  • mutation: 更改vuex的store中的状态的唯一方法是提交mutation,而mutation是在回调函数里直接修改state值
  • action类似于mutation,不同在于action提交的是mutation,而不是直接更改state,而且可以包含异步操作

3.4 Vue响应式原理简述

Vue在初始化的时候,递归遍历data,通过 definedProper对数据进行 setget绑定,
然后创建一个 dep对象, dep对象用于依赖收集,实现一个发布订阅的模式,实现data和渲染视图watcher的订阅,
当数据触发get查询时,会将当前的watcher对象加入到dep中,
当data变化的时候,会触发set通知所有用到这个data的watcher对象去触发生成新的DOM树,然后对比新旧DOM树,根据不同点去修改真实DOM树

  • 在一个vue实例被创建的时候,data中的变量会被vue遍历property,并使用Object.property把这些property全部转换为getter/setter;getter/setter使用者是不可见的,当data中的数据发生变更和使用的时候会被调用。vue实例有watcher实例,用于监听data对象并收集数据property记录为依赖,然后调用setter和getter实现实例的重新渲染

3.5 Vue常用组件库

vue-table,vue-datepicker,vue-progressbar,vue-editor,vue-lazyload-img

3.6、keep-alive的原理,使用有什么问题?如何解决?

在 created钩子函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode实例进行渲染。

3.7、完整的导航守卫解析流程

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