Vue面试题

218 阅读6分钟

1.v-show和v-if的区别

  • v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
  • v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (display:none)

使用场景

  • v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
  • v-show 适用于需要非常频繁切换条件的场景

2.为何在v-for中用key

必须用key,且不能是index和random
diff算法中通过tag和key来判断,是否是sameNode
减少渲染次数,提升渲染性能


如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快 相关代码如下

// 判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用
function isSameVnode(oldVnode, newVnode) {
  return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key;
}

// 根据key来创建老的儿子的index映射表  类似 {'a':0,'b':1} 代表key为'a'的节点在第一个位置 key为'b'的节点在第二个位置
function makeIndexByKey(children) {
  let map = {};
  children.forEach((item, index) => {
    map[item.key] = index;
  });
  return map;
}
// 生成的映射表
let map = makeIndexByKey(oldCh);

3.描述vue组件生命周期(父子组件)

单组件:

  • beforeCreate 在实例初始化之后,数据观测observer 和event、watcher事件配置之前被调用
  • created 实例已经创建完成,在这一步,以下配置被完成
    • 数据观测
    • 属性和方法的运算
    • watch/event时间回调
    • $el尚未生成
  • beforeMount 在挂载之前被调用,render尚未被调用
  • mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用
  • beforeUpdate 数据更新时,被调用,发生在虚拟Dom重新渲染和打补丁之前
  • update 由于数据更改导致的虚拟Dom重新渲染和打补丁,在这之后调用
  • beforeDestroy 实例销毁之前调用
  • destroyed 实例销毁之后调用,调用后Vue实例的所有东西都会被解绑,所有的事件监听会被移除,子实例被销毁,该钩子在服务端渲染期间不被调用
  • keep-alive(activated & deactivated)

vue 的父组件和子组件生命周期钩子函数执行顺序?

  • 加载渲染过程
    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
  • 子组件更新过程
    父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
  • 父组件更新过程
    父 beforeUpdate -> 父 updated
  • 销毁过程
    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

4.vue组件如何通讯(常见)

  • props
  • $emit
  • $attr
  • $listener
  • provide inject (隔代通信)
  • parentparent children
  • vuex

5.描述组件渲染和更新的过程

组件渲染与更新

Vue 原理的三大模块分别为响应式、vdom 和模板编译,前面已经分别学习过,现在通过总结渲染过程来将它们串起来回顾。

初次渲染过程

  • Step1:解析模板为 render 函数(这步操作或在开发中通过 vue-loader 已完成)
  • Step2:触发响应式,监听 data 属性 getter 和 setter(下一步执行 render 函数可能会用到 getter)
  • Step3:执行 render 函数,生成 vnode,渲染节点 patch(elem, vnode)

更新过程

  • Step1:修改 data,触发 setter(前提是该 data 此前在 getter 中已被监听,即模板中被引用的 data)
  • Step2:重新执行 render 函数,生成 newVnode
  • Step3:更新节点 patch(vnode, newVnode)

其中 vnode 和 newVnode 的最小差异由 patch 的 diff 算法计算。

完整流程图

组件渲染与更新的完整流程图如下所示:

  • 黄色方框为 render 函数(此时模板已经编译完),它会生成 vnode(绿色 Virtual DOM Tree)。
  • 黄色方框在执行 render 时,会触发(Touch)紫色圆圈(Data)里面的 getter。
  • 紫色圆圈(Data)里的 getter 触发时,会收集依赖,模板里哪个变量的 getter 被触发了,就会将相应变量观察起来(蓝色圆圈 Watcher)
  • 一旦修改了 Data,就会通知 Watcher,如果修改的 data 是之前作为依赖被观察的,则重新触发渲染(re-render)。

image.png

6.双向数据绑定v-model的实现原理

简单的说,就是 :value 和 @input 的结合使用,v-model就是他们两个的语法糖。
input元素的value - this.name
绑定input事件 this.name = $event.target.value
data更新触发re-render


v-model 只是语法糖而已
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件 在普通标签上
    <input v-model="sth" />  //这一行等于下一行
    <input v-bind:value="sth" v-on:input="sth = $event.target.value" />

在组件上

<currency-input v-model="price"></currentcy-input>
<!--上行代码是下行的语法糖
 <currency-input :value="price" @input="price = arguments[0]"></currency-input>
-->

<!-- 子组件定义 -->
Vue.component('currency-input', {
 template: `
  <span>
   <input
    ref="input"
    :value="value"
    @input="$emit('input', $event.target.value)"
   >
  </span>
 `,
 props: ['value'],
})

7.对MVVM的理解

映射关系简化,隐藏controller MVVM在MVC的基础上,把控制层隐藏掉了。

Vue不是一个MVVM框架,它是一个视图层框架。

ViewModal是一个桥梁,将数据和视图进行关联。 image.png

8.computed 和 watch 的区别和运用的场景

computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,它可以设置 getter 和 setter。

watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

计算属性一般用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑

答案引用:juejin.cn/post/696122…

9.为何组件data必须是一个函数

因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了

10.ajax请求应该放在哪个生命周期

可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间;
  • ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

作者:Big shark@LX
链接:juejin.cn/post/696122… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

11.如何将组件所有props传递给子组件

 $props:<User v−bind="$props" />

12.如何自己实现v-model

image.png

13.多个组件有相同的逻辑,如何抽离?

mixin
以及mixin的一些缺点

mixin的缺点:
变量来源不明确,不利于阅读
多mixin可能会造成命名冲突
mixin和组件可能出现多对多的关系,复杂度较高

mixin和组件的顺序?

  1. 组件 的 data, methods 优先级高于mixin data, methods 优先级
  2. 生命周期函数,先执行 mixin 里面的,再执行组件里面的
  3. 组件的 自定义属性 优先级高于 mixin 自定义属性 优先级

14.何时要使用异步组件

加载大组件
路由异步加载

15.何时需要使用keep-alive

缓存组件,不需要重复渲染
如多个静态tab页的切换
优化性能
juejin.cn/post/704307…

16.何时需要使用beforeDestory

解绑自定义事件 event.$off
清除定时器
解绑自定义的DOM时间,如window scroll等

17.什么是作用域插槽

image.png

18.Vuex中action和mutation有何区别

juejin.cn/post/696122…
action中处理异步,mutation不可以
mutation做原子操作
action可以整合多个mutation

19.Vue-router常用的路由模式

hash默认
H5 history(需要服务端支持)

vuerouter的两种模式的区别

  • vue-router中有三种模式,分别是hash、history、abstract
  • abstract在不支持浏览器的API换景使用
  • hash模式兼容性好,但是不美观,不利于SEO
  • history美观,historyAPI+popState,但是刷新会出现404
    作者:海明月
    链接:juejin.cn/post/704307…

vue-router 中常用的路由模式实现原理

hash 模式

  1. location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
  2. 可以为 hash 的改变添加监听事件

window.addEventListener("hashchange", funcRef, false);

history 模式

利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。

这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

特点:虽然美观,但是刷新会出现 404 需要后端进行配置

作者:Big shark@LX
链接:juejin.cn/post/696122…

20.如何配置Vue-router异步加载

image.png

21.请用vnode描述一个DOM结构

image.png

22.监听data变化的核心API是什么

Object.defineProperty
以及深度监听、监听数组
有何缺点

23.Vue如何监听数组变化

vue中对数组没有进行defineProperty,而是重写了数组的7个方法。 分别是:

  • push
  • shift
  • pop
  • splice
  • unshift
  • sort
  • reverse

因为这些方法都会改变数组本身。

数组里的索引和长度是无法被监控的。


Object.defineProperty不能监听数组变化
重新定义原型,重写push pop等方法,实现监听
Proxy可以原生支持监听数组变化

24.请描述响应式原理----面试必问

数组和对象类型的值变化的时候,通过defineReactive方法,借助了defineProperty,将所有的属性添加了gettersetter。用户在取值和设置的时候,可以进行一些操作。

缺陷:只能监控最外层的属性,如果是多层的,就要进行全量递归。

get里面会做依赖搜集(dep[watcher, watcher])set里面会做数据更新(notify,通知watcher更新)

作者:海明月

链接:juejin.cn/post/704307…

25.diff算法的时间复杂度

O(n)
在O(n^3)基础上做了一些调整

26.简述diff算法过程

patch(elem,vnode)和patch(vnode,newVnode)
patchVnode和addVnodes和removeVnodes
updateChildren(key的重要性)

27.Vue为何是异步渲染,$nextTick何用

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染, 所以为了性能考虑。vue会在本轮数据更新后,再去异步更新视图。


异步渲染(以及合并data修改),以提高渲染性能
$nextTick在DOM更新完之后,触发回调

nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法

28.Vue常见性能优化方式

合理使用v-show和v-if
合理使用computed
v-for时价key,以及避免和v-if同时使用
自定义事件、DOM事件及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深
使用vue-loader在开发环境做模板编译(预编译)
webpack层面的优化(后面会讲)
前端通用的性能优化,如图片懒加载
使用SSR


  • 对象层级不要过深,否则性能就会差

  • 不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)

  • v-if 和 v-show 区分使用场景

  • computed 和 watch 区分使用场景

  • v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if

  • 大数据列表和表格性能优化-虚拟列表/虚拟表格

  • 防止内部泄漏,组件销毁后把全局变量和事件销毁

  • 图片懒加载

  • 路由懒加载

  • 第三方插件的按需引入

  • 适当采用 keep-alive 缓存组件

  • 防抖、节流运用

  • 服务端渲染 SSR or 预渲染