Table of Content
- 前言
-
- 一、基础篇
- 二、VUEX篇
- 三、常规篇
-
- 1. computed 和 watch 的区别和运用的场景?
- 2. watch中的deep:true是如何实现的?
- 3. Vue2.x组件通信有哪些方式?
- 4. 说一下v-if和v-show的区别
- 5. 为什么 v-for 和 v-if 不建议用在一起
- 6. 组件中的data为什么是一个函数?
- 7. 子组件为什么不可以修改父组件传递的Prop?/怎么理解vue的单向数据流?
- 8. v-model是如何实现双向绑定的?
- 9. nextTick的实现原理是什么?
- 10. Vue不能检测数组的哪些变动?Vue 怎么用 `vm.$set()` 解决对象新增属性不能响应的问题 ?
- 11. Vue事件绑定原理是什么?
- 12. 说一下虚拟Dom以及key属性的作用
- 13. 为什么不建议用index作为key?
- 14. vue中v-html会导致哪些问题?
- 四、生命周期篇
- 五、路由篇
- 六、进阶篇
- 总结
前言
本篇博客主要记录Vue
面试时,经常会被问到的问题,分享大家,如果有错误之处,欢迎指正(如果感觉写的对你有用,不妨点个赞😄)
文章分为五大板块:基础篇,常规篇,Vuex,生命周期,路由,进阶篇。每个板块里面都对应一些常见的Vue面试题目。
一、基础篇
1. 说说你对MVVM的理解
- Model-View-ViewModel的缩写,Model代表数据模型,View代表UI组件,ViewModel将Model和View关联起来
- 数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据
了解mvc/mvp/mvvm的区别
MVVM:传统前端将数据渲染到页面上,MVVM模式不需要用户收到操作dom元素,将数据绑定到viewModel
层,会自动渲染到页面上,视图变化会通知viewModel
层更新数据,viewModel
为MVVM中的桥梁。
2. Vue2.x响应式数据/双向绑定原理
- Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。其中,View变化更新Data,可以通过事件监听的方式来实现,所以 Vue数据双向绑定的工作主要是如何根据Data变化更新View。
- 简述:
- 深入理解:
- 监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用
Object.defineProperty()
对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。 - 解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- 订阅者 Watcher:
Watcher
订阅者是Observer
和Compile
之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式 - 订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
- 监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用
默认Vue在初始化数据时,会给data中的属性使用Object.defineProperty
重新定义所有属性,当页面取到对应属性时。会进行依赖收集(收集当前组的watcher
)如果属性发生变化会通知相关依赖进行更新操作
3. 你知道Vue3.x响应式数据原理吗?
- Vue3.x改用Proxy替代Object.defineProperty。
- 因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
- Proxy只会代理对象的第一层,Vue3是怎样处理这个问题的呢?
- 判断当前
Reflect.get
的返回值是否为Object
,如果是则再通过reactive
方法做代理, 这样就实现了深度观测。 - 监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
- 判断当前
4. Proxy 与 Object.defineProperty 优劣对比
- Proxy 的优势如下:
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于
apply、ownKeys、deleteProperty、has
等等是Object.defineProperty
不具备的; - Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而
Object.defineProperty
只能遍历对象属性直接修改; - Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
- Proxy 有多达 13 种拦截方法,不限于
Object.defineProperty
的优势如下:- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用
polyfill
磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用
二、VUEX篇
1. Vuex 是什么?
运用到了js设计模式中的单例模式,单例模式想要做到的是,不管我们尝试去创建多少次,它都只给你返回第一次所创建的那唯一的一个实例。
当使用了Vuex,使用其Store进行数据管理。数据的处理就会变得简便。
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 ——Vuex官方文档
- 主要包括以下几个模块:
State
:定义了应用状态的数据结构,可以在这里设置默认的初始状态。Getter
:允许组件从 Store 中获取数据,mapGetters
辅助函数仅仅是将store
中的getter
映射到局部计算属性。Mutation
:是唯一更改store
中状态的方法,且必须是同步函数。Action
:用于提交mutation
,而不是直接变更状态,可以包含任意异步操作。Module
:允许将单一的Store
拆分为多个store
且同时保存在单一的状态树中。
2. 什么情况下使用 Vuex?
- 如果应用够简单,最好不要使用 Vuex,一个简单的 store 模式即可
- 需要构建一个中大型单页应用时,使用Vuex能更好地在组件外部管理状态
3. Vuex和单纯的全局对象有什么区别?
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若
store
中的状态发生变化,那么相应的组件也会相应地得到高效更新。 - 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交
(commit) mutation
。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
4. 为什么 Vuex 的 mutation 中不能做异步操作?
- Vuex中所有的状态更新的唯一途径都是
mutation
,异步操作通过 Action 来提交mutation
实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。 - 每个
mutation
执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现time-travel
了。如果mutation
支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
5. 新增:vuex的action有返回值吗?返回的是什么?
store.dispatch
可以处理被触发的action
的处理函数返回的 Promise,并且store.dispatch
仍旧返回Promise
Action
通常是异步的,要知道action
什么时候结束或者组合多个action
以处理更加复杂的异步流程,可以通过定义action
时返回一个promise
对象,就可以在派发action的时候就可以通过处理返回的Promise
处理异步流程
一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
6. 新增:为什么不直接分发mutation,而要通过分发action之后提交 mutation变更状态
mutation
必须同步执行,我们可以在action
内部执行异步操作- 可以进行一系列的异步操作,并且通过提交
mutation
来记录action
产生的副作用(即状态变更)
7. action和mutation区别
mutation
是同步更新数据(内部会进行是否为异步方式更新数据的检测)action
异步操作,可以获取数据后调用mutation
提交最终数据。
三、常规篇
1. computed 和 watch 的区别和运用的场景?
computed
:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;watch
:没有缓存性,更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听- 运用场景:
- 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用watch选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
2. watch中的deep:true是如何实现的?
- 当用户指定了
watch
中的deep
属性为true
时,如果当前监控的值是数组类型。会对对象中的每一项进行求值,此时会将当前watcher
存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新。
3. Vue2.x组件通信有哪些方式?
-
父子组件通信
- 事件机制(**父->子props,子->父
$on、$emit
) - 获取父子组件实例
$parent、$children
- Ref 获取实例的方式调用组件的属性或者方法
- Provide、inject (不推荐使用,组件库时很常用)
- 事件机制(**父->子props,子->父
-
兄弟组件通信
- eventBus 这种方法通过一个空的 Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
Vue.prototype.`$bus` = new Vue
-
Vuex
-
跨级组件通信
- Vuex
$attrs、$listeners
- Provide、inject