前言
本文讲解 8 道高频出现的 Vue 面试题及答案。
复习前端面试的知识,是为了巩固前端的基础知识,最重要的还是平时的积累!
注意
:文章的题与题之间用下划线分隔开,答案仅供参考。
笔者技术博客首发地址 GitHub[1],欢迎关注。
Vue
对 MVC、MVP 、MVVM 的理解
MVC 模式的意思是,软件可以分成三个部分。
视图(View):用户界面。
控制器(Controller):业务逻辑。
模型(Model):数据保存。
各部分之间的通信方式如下。
View 传送指令到 Controller
Controller 完成业务逻辑后,要求 Model 改变状态
Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的(逆时针)。
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
各部分之间的通信,都是双向的(顺时针)。
View 与 Model 不发生联系,都通过 Presenter 传递。
View 非常薄,不部署任何业务逻辑,称为 "被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View 的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。
如何理解 Vue 是异步执行 DOM 更新的 ?
Vue 是异步执行 DOM 更新。
只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。
然后,在下一个的事件循环
tick
中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = 'new value' ,该组件不会立即重新渲染。
当刷新队列时,组件会在事件循环队列清空时的下一个
tick
更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。
虽然 Vue.js 通常鼓励开发人员沿着 “数据驱动” 的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
深入响应式原理
如何追踪变化
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转 getter/setter。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
观察者订阅了可观察对象,当可观察对象发布事件,则就直接调度观察者的行为,所以这里观察者和可观察对象其实就产生了一个依赖的关系。
说下对 Virtual DOM 算法的理解 ?
包括几个步骤:
1、用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中;
2、当状态变更的时候,重新构造一棵新的对象树,然后用新的树和旧的树进行比较,记录两棵树差异;
3、把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
比较两棵虚拟 DOM 树的差异
比较两棵 DOM 树的差异是 Virtual DOM 算法最核心的部分,这也是所谓的 Virtual DOM 的 diff 算法。两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动 DOM 元素。
所以 Virtual DOM 只会对同一个层级的元素进行对比:
上面的 div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对比。这样算法复杂度就可以达到 O(n)。
深度优先遍历,记录差异
在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记:
在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比。如果有差异的话就记录到一个对象里面。
Virtual DOM 算法主要是实现上面步骤的三个函数:element,diff,patch。然后就可以实际的进行使用:
// 1. 构建虚拟 DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
])
// 2. 通过虚拟 DOM 构建真正的 DOM
var root = tree.render()
document.body.appendChild(root)
// 3. 生成新的虚拟 DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
// 4. 比较两棵虚拟 DOM 树的不同
var patches = diff(tree, newTree)
// 5. 在真正的 DOM 元素上应用变更
patch(root, patches)
当然这是非常粗糙的实践,实际中还需要处理事件监听等;生成虚拟 DOM 的时候也可以加入 JSX 语法。这些事情都做了的话,就可以构造一个简单的 ReactJS 了。
非父子组件如何通信 ?
Vue 官网介绍了非父子组件通信方法:
在 bus.js 里面 写入下面信息
import Vue from 'vue'
export default new Vue();
在需要通信的组件都引入 Bus.js
<template>
<div id="emit">
<button @click="bus">按钮</button>
</div>
</template >
import Bus from './bus.js'
export default {
data() {
return {
message: ''
}
},
methods: {
bus() {
Bus.$emit('msg', '我要传给兄弟组件们,你收到没有')
}
}
}
在钩子函数中监听 msg 事件:
<template>
<div id="on">
<p>{{message}}</p>
</div>
</template>
import Bus from './bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
let self = this
Bus.$on('msg', (e) => {
self.message = e
console.log(`传来的数据是:${e}`)
})
}
}
最后 p 会显示来自 $emit 传来的信息。
什么情况下我应该使用 Vuex ?
虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此,如果您的应用够简单,您最好不要使用 Vuex。一个简单的 global event bus[2] 就足够您所需了。
但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
Vue 过程图解
Vue 生命周期过程图解
Vue 响应式原理
Vue 过程图解
Vuex
Vue 经典面试相关文章
1. 对 Vue、Vue-Router、Vuex 源码与架构的理解[3]
2. Vue 生命周期[4]
3. 详解 Vue 生命周期[5]
4. Vue 组件间通信六种方式(完整版)[6]
5. Vue 学习笔记-实现一个分页组件[7]
最后
前端硬核面试专题的完整版在此:前端硬核面试专题[8],包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 数据结构与算法 + Git 。
如果觉得本文还不错,记得给个 star , 你的 star 是我持续更新的动力!。
往期精文
3. JavaScript 数据结构与算法之美 - 十大经典排序算法汇总
4. 56 道高频 JavaScript 与 ES6+ 的面试题及答案
听说点 在看
的永远 18 岁~
参考资料
[1]
GitHub: https://github.com/biaochenxuying/blog
[2]global event bus: https://cn.vuejs.org/v2/guide/components.html#%E7%9B%91%E5%90%AC%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BA%8B%E4%BB%B6
[3]1. 对 Vue、Vue-Router、Vuex 源码与架构的理解: https://github.com/biaochenxuying/vue-family-mindmap
[4]2. Vue 生命周期: https://www.jianshu.com/p/304a44f7c11b
[5]3. 详解 Vue 生命周期: https://segmentfault.com/a/1190000011381906
[6]4. Vue 组件间通信六种方式(完整版): https://juejin.im/post/5cde0b43f265da03867e78d3
[7]5. Vue 学习笔记-实现一个分页组件: https://www.jianshu.com/p/d17d8e35deda
[8]前端硬核面试专题: https://github.com/biaochenxuying/blog/blob/master/interview/fe-interview.md