本文会列举一些平时面试时问到的问题和答案,并说明面试官在当时问到这个问题时所期望对方的回答:
vue生命周期(钩子函数)
问题
请说一下vue的生命周期函数(钩子函数)。
问题描述
首先关于生命周期函数,一般我的第一个问题就是这个,我认为是每个使用vue的都要清楚的,如果这个问题答的问题很大其实我都不太想继续往下进行了。 即使英语不标准(我就是不标准的人,并不是说这是个问题)也要去把关键点说清楚,哪个地方有ed哪个地方没有ed其实是很关键的,或者可以手写下来,因为常用的就是created和mounted所以前4个可以清楚的手写出来并不难,后面4个不去详细说明都没事。(我自己工作中基本没用过后面4个)
在哪个周期能够首次拿到data数据和在哪个周期能够首次拿到mounted中的dom元素,如果没有说到这个问题,我一般会一直往下问,直到他说出来这两个答案。
期望答案
beforeCreate、created(此时需说明可以在created中首次拿到data中定义的数据)、beforeMount、mounted(此时需说明dom树渲染结束,可访问dom结构)、beforeUpdate、updated、beforeDestroy、destroyed
computed中的getter和setter
问题
computed中的getter和setter。
问题描述
很多情况,我问到这个问题的时候对方的回答都是vue的getter和setter、订阅者模式之类的回答,我就会直接说问的并不是这个,而是computed,直接让对方说computed平时怎么使用。
很多时候得到的回答是computed的默认方式,只使用了其中的getter,就会继续追问如果想要再把这个值设置回去要怎么做,当然一般会让问到这个程度的这个问题他都答不上来了。
期望答案
watch监听对象
问题
如何watch监听一个对象内部的变化。
问题描述
这个问题我感觉是一个不应该不会的问题,可是我遇到的人大部分都没有给出我所期望的答案,有些人会说直接监听obj,好一点的会说直接点出来监听obj.key,但是很少有人回答deep,开始我还会去问immediate,但是太多人不知道了,所以后来我就只问监听对象了,只有回答出deep的才会去问immediate的作用。
期望答案
如果只是监听obj内的某一个属性变化,可以直接obj.key进行监听。
如果对整个obj深层监听
immediate的作用:当值第一次进行绑定的时候并不会触发watch监听,使用immediate则可以在最初绑定的时候执行。
v-for循环key的作用
问题
v-for循环时为什么要加key。
问题描述
问这个问题时,好多人再先回答的都是页面有警告,编辑器有提示,我会直接说不考虑报错和提示的问题,或者会问如果不加key的话,页面会不会出现什么异常情况。有的人会说是一个标识,标识他的唯一性,我会继续追问为什么要标识唯一性呢,不加又怎么样?
期望答案
vue的dom渲染是虚拟dom,数据发生变化时,diff算法会只比较更改的部分,如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
举例说明:有一个列表我们现在在中间插入了一个元素,diff算法会默认复用之前的列表并在最后追加一个,如果列表存在选中一类的状态则会随着复用出现绑定错误的情况而不是跟着原元素,key的作用就可以给他一个标识,让状态跟着数据渲染。
$nextTick
问题
$nextTick用过吗,有什么作用。
问题描述
问到这个问题时,很多人都会说到可以处理异步,而往下追问为什么要用nextTick,他解决了什么问题,不用他会怎么样的时候就很多人说不上来了。
期望答案
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。(官网解释)
解决的问题:有些时候在改变数据后立即要对dom进行操作,此时获取到的dom仍是获取到的是数据刷新前的dom,无法满足需要,这个时候就用到了$nextTick。
$set
问题
vue中的$set用过吗,为什么要用它,解决了什么问题
问题描述
这个问题知道的人就基本都能说出来,但是不知道的就是一点不了解,有的还会说到es6的set结构
期望答案
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = ‘hi’)(官方示例)
我自己的理解就是,在vue中对一个对象内部进行一些修改时,vue没有监听到变化无法触发视图的更新,此时来使用$set来触发更新,使视图更新为最新的数据。
组件间的传值
组件间的传值方式
问题
说一下组件间的传值方式,你知道的所有方式都说一下
问题描述
这个问题其实就是想看官方文档有没有具体看过,因为很多传值方式官方文档上有描述,但是项目中用的相对较少。
基本都能回答上来,父传子:props;子传父:$emit;兄弟:eventbus;vuex;有一些会说到sessionStorage和localStorage、路由传参(这个答案其实并不是我想要问的,不过也可以实现一定的传值)
期望答案
1、provide / inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
2、Vue.observable
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景
3、$ attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop
时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
4、$ listeners
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
5、props
props 可以是数组或对象,用于接收来自父组件的数据,一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop
6、$ emit
子組件调用父组件中的方法时使用$emit(‘方法名’,参数),触发当前实例上的事件。附加参数都会传给监听器回调
7、eventbus
在要相互通信的兄弟组件之中,都引入一个新的vue实例,然后通过分别调用这个实例的事件触发和监听来实现通信和参数传递。
8、vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
9、$parent / $children
$parent
指定已创建的实例之父实例,在两者之间建立父子关系。
子实例可以用
this.$parent
访问父实例,子实例被推入父实例的$children
数组中
$children
当前实例的直接子组件。需要注意$children
并不保证顺序,也不是响应式的。
10、$root
当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己
以上就是前端面试时面试官想要听到什么答案(关于一些Vue的问题)的详细内容
0.那你能讲一讲MVVM吗?
- MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。
- Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,
- 数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。
1.简单说一下Vue2.x响应式数据原理
- Vue在初始化数据时,会使用Object.defineProperty重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher)
- 如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。
2.那你知道Vue3.x响应式数据原理吗?
- Vue3.x改用
Proxy
替代Object.defineProperty
。因为Proxy可以直接监听对象和数组
的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?
- 判断当前
Reflect.get
的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
- 我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
面试官抬起了头。心里暗想
(这小子还行,比上两个强,应该是多多少少看过Vue3的源码了)
3.再说一下vue2.x中如何监测数组变化
- 使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。
- 这样当调用数组api时,可以通知依赖更新。
- 如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
4.nextTick知道吗,实现原理是什么?
- 在下次 DOM 更新循环结束之后执行延迟回调。
- nextTick主要使用了宏任务和微任务。
- 根据执行环境分别尝试采用
Promise
MutationObserver
setImmediate
如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
(关于宏任务和微任务以及事件循环可以参考我的另两篇专栏)
(看到这你就会发现,其实问框架最终还是考验你的原生JavaScript功底)
浏览器中JavaScript的事件循环
Node.js事件循环
5.说一下Vue的生命周期
beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。
created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
beforeMount发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
mounted在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
beforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
updated发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
beforeDestroy发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
从源码解读Vue生命周期,让面试官对你刮目相看
6.你的接口请求一般放在哪个生命周期中?
- 接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。
7.再说一下Computed和Watch
Computed本质是一个具备缓存的watcher,依赖的响应式属性变化才会重新计算并且更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。
Watch没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。常用于监听某一个值,当被监听的值发生变化时,执行对应的操作。 打开deep:true选项会深度监听对象中的属性,对对象中的每一项进行监听。 immediate: true 选项表示,初始化时就会先执行一遍该监听对应的操作
8.说一下v-if和v-show的区别
- 当条件不成立时,v-if不会渲染DOM元素,v-show操作的是样式(display),切换当前DOM的显示和隐藏。
9.组件中的data为什么是一个函数?
- 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。
- 如果data是对象的话,对象属于引用类型,会影响到所有的实例。
- 所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
10.说一下v-model的原理
- v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。
- 可以通过model属性的prop和event属性来进行自定义。
- 原生的v-model,会根据标签的不同生成不同的事件和属性。
11.Vue事件绑定原理说一下
- 原生事件绑定是通过addEventListener绑定给真实元素的,组件事件绑定是通过Vue自定义的$on实现的。
面试官:(这小子基础还可以,接下来我得上上难度了)
12.Vue模版编译原理知道吗,能简单说一下吗?
- 简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:
1、生成AST树
2、优化
3、codegen
-
首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
-
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。
-
那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
-
编译的最后一步是将优化后的AST树转换为可执行的代码。
面试官:(精神小伙啊,有点东西,难度提升,不信难不倒你)
13.Vue2.x和Vue3.x渲染器的diff算法分别说一下
简单来说,diff算法有以下过程
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心diff)
- 递归比较子节点
- 正常Diff两个树的时间复杂度是O(n^3) ,但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都
为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了 ivi算法和 inferno算法
在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)
该算法中还运用了动态规划的思想求解最长递归子序列。
(看到这你还会发现,框架内无处不蕴藏着数据结构和算法的魅力)
面试官:(可以可以,看来是个苗子,不过自我介绍属实有些无聊,下一题)
(基操,勿6)
14.再说一下虚拟Dom以及key属性的作用
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。
Vue2的Virtual DOM借鉴了开源库snabbdom的实现。
Virtual DOM
本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)
VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。
key的作用是尽可能的复用 DOM 元素。
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
15.keep-alive了解吗
keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性include/exclude,允许组件有条件的进行缓存。
两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
keep-alive的中还运用了LRU(Least Recently Used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高 。
(又是数据结构与算法,原来算法在前端也有这么多的应用)
16.Vue中组件生命周期调用顺序说一下
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
17.Vue2.x组件通信有哪些方式?
- 父子组件通信
父->子props, 子->父 $on、$emit
- 获取父子组件实例
$parent
、$children
Ref
获取实例的方式调用组件的属性或者方法
-
Provide、inject
官方不推荐使用,但是写组件库时很常用 -
兄弟组件通信
Event Bus
实现跨组件通信 Vue.prototype.$bus = new Vue
Vuex
- 跨级组件通信
$attrs、$listeners
18.SSR了解吗?
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。
SSR有着更好的SEO、并且首屏加载速度更快等优点。
不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。
还有就是服务器会有更大的负载需求。
19.你都做过哪些Vue的性能优化?
编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
SEO优化
预渲染
服务端渲染SSR
打包优化
压缩代码
使用cdn加载第三方模块
sourceMap优化
用户体验
骨架屏
PWA(渐进式WEB应用)
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
(优化是个大工程,会涉及很多方面,这里申请另开一个专栏)
20.hash路由和history路由实现原理说一下
location.hash的值实际就是URL中#后面的东西。
history实际采用了HTML5中提供的API来实现,主要有history.pushState()和history.replaceState()。
面试官拿起旁边已经凉透的咖啡,喝了一口。
(我难道问不倒这小子了么)
学习链接:你要的Vue面试题都在这里
Vue 的十道选择题,你能答对几道?
1. Vue 实例的 data 属性,可以在哪些生命周期中获取到?
A. beforeCreate
B. created
C. beforeMount
D. mounted
正确答案》BCD
2. 下列对 Vue 原理的叙述,哪些是正确的?
A. Vue 中的数组变更通知,通过拦截数组操作方法而实现
B. 编译器目标是创建渲染函数,渲染函数执行后将得到 VNode 树
C. 组件内 data 发生变化时会通知其对应 watcher,执行异步更新
D. patching 算法首先进行同层级比较,可能执行的操作是节点的增加、删除和更新
正确答案》 ABCD
3. 对于 Vue 中响应式数据原理的说法,下列哪项是不正确的? D
A. 采用数据劫持方式,即 Object.defineProperty() 劫持 data 中各属性,实现响应式数据
B. 视图中的变化会通过 watcher 更新 data 中的数据
C. 若 data 中某属性多次发生变化,watcher 仅会进入更新队列一次
D. 通过编译过程进行依赖收集
正确答案》BD
4. 下列说法不正确的是哪项? B
A. key 的作用主要是为了高效地更新虚拟 DOM
B. 若指定了组件的 template 选项,render 函数不会执行
C. 使用 vm.
n
e
x
t
T
i
c
k
可
以
确
保
获
得
D
O
M
异
步
更
新
的
结
果
D
.
若
没
有
e
l
选
项
,
v
m
.
nextTick 可以确保获得 DOM 异步更新的结果 D. 若没有 el 选项,vm.
nextTick可以确保获得DOM异步更新的结果D.若没有el选项,vm.mount(dom) 可将 Vue 实例挂载于指定元素上
正确答案》B
5. 下列关于 Vuex 的描述,不正确的是哪项? A
A. Vuex 通过 Vue 实现响应式状态,因此只能用于 Vue
B. Vuex 是一个状态管理模式
C. Vuex 主要用于多视图间状态全局共享与管理
D. 在 Vuex 中改变状态,可以通过 mutations 和 actions
正确答案》D
6. 关于 Vue 组件间的参数传递,下列哪项是不正确的?
A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit(‘say’) 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据
正确答案》B
7. 下列关于 vue-router 的描述,不正确的是哪项? D
A. vue-router 的常用模式有 hash 和 history 两种
B. 可通过 addRoutes 方法动态添加路由
C. 可通过 beforeEnter 对单个组件进行路由守卫
D. vue-router 借助 Vue 实现响应式的路由,因此只能用于 Vue
正确答案》 C
8. 下列说法不正确的是哪项? B
A. 可通过 this.
p
a
r
e
n
t
查
找
当
前
组
件
的
父
组
件
B
.
可
使
用
t
h
i
s
.
parent 查找当前组件的父组件 B. 可使用 this.
parent查找当前组件的父组件B.可使用this.refs 查找命名子组件
C. 可使用 this.$children 按顺序查找当前组件的直接子组件
D. 可使用 $root 查找根组件,并可配合 children 遍历全部组件
正确答案》C
9. 下列关于 v-model 的说法,哪项是不正确的?
A. v-model 能实现双向绑定
B. v-model 本质上是语法糖,它负责监听用户的输入事件以更新数据
C. v-model 是内置指令,不能用在自定义组件上
D. 对 input 使用 v-model,实际上是指定其 :value 和 :input
正确答案》C
10. 关于 Vue 的生命周期,下列哪项是不正确的?
A. DOM 渲染在 mounted 中就已经完成了
B. Vue 实例从创建到销毁的过程,就是生命周期
C. created 表示完成数据观测、属性和方法的运算和初始化事件,此时 $el 属性还未显示出来
D. 页面首次加载过程中,会依次触发 beforeCreate,created,beforeMount,mounted,beforeUpdate,updated
正确答案》D
旁边的是我答的,只拿了40分,太惨了~~~~