目录
1. 定制软件你怎样理解Vue
Vue通过MVVM定制软件思想实现数据的,定制软件数据驱动页面视图。
- Vue定制软件是数据驱动的框架,定制软件我们不必纠结于DOM定制软件元素的获取与操作,定制软件变动数据时,Vue定制软件的底层会自动的帮助我定制软件们更新视图层。(VM定制软件这一层不必我们去操作)
- JQuery定制软件中我们是面向DOM开发,Vue定制软件是面向数据开发(Model层最重要,其次View层)
MV-VM的理解
- Model-View-ViewModel
- Model - 数据层,定制软件如计数器案例中的obj定制软件或是从网络上请求的数据
- View - 视图层,DOM层
- ViewModel - 视图模型 - 是Model与View定制软件沟通的桥梁
- 定制软件定制软件一方面实现Data Binding,数据绑定,将Model定制软件的改变反应到View中;
- 一方面实现DOM Listening,监听到DOM中的事件,定制软件并根据情况更改对应Data
Vue定制软件是怎么实现VM层的
- 虚拟DOM
- Object.defineProperty
- 定制软件当你把一个普通的 JavaScript 对象传入 Vue 实例作为
data
选项,Vue 定制软件将遍历此对象所有的 property,并使用 把这些 property 全部转为 。Object.defineProperty
是 ES5 中的特性,这也就是 Vue 不支持 IE8 定制软件以及更低版本浏览器的原因。
- 定制软件当你把一个普通的 JavaScript 对象传入 Vue 实例作为
2.
定制软件是通过采用数据劫持结合发布者-定制软件订阅者模式定制软件的方式来实现的。通过Object.defineProperty()定制软件来劫持各个属性的setter,getter。修改触发set方法赋值,获取触发get方法取值,定制软件在数据变动时发布消息给订阅者,定制软件触发相应的回调并通过定制软件数据劫持发布信息。
Vue 定制软件主要通过以下 4 定制软件个步骤来实现数据双向绑定的:
-
定制软件实现一个监听器 Observer:定制软件对数据对象进行遍历,定制软件包括子属性对象的属性,利用 Object.defineProperty() 定制软件对属性都加上 setter 和 getter。这样的话,定制软件给这个对象的某个值赋值,就会触发 setter,定制软件那么就能监听到了数据变化。
-
**实现一个解析器 Compile:**解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
-
**实现一个订阅者 Watcher:**Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
-
**实现一个订阅器 Dep:**订阅器采用发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
3. v-model双向绑定的原理
v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据。
Object.defineproperty()重新定义(set方法)对象设置属性值和(get方法)获取属性值的操纵来实现的。
- 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者
- 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
- 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
4. Vue不能检测数组和对象的变化
因为 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
追问:如何解决上述问题?
对于对象:
Vue.set(vm.someObject, 'b', 2)
- 1
也可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名:
this.$set(this.someObject,'b',2)this.$delete(this.someObject,'b') 删除旧属性
- 1
- 2
添加多个属性:( 直接使用Object.assign()
或_.extend()
无法触发更新 )
应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
- 1
- 2
对于数组:
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
// Vue.set或vm.$setVue.set(vm.items, indexOfItem, newValue)vm.$set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue)//解决第2点,splice若只设置一个参数,则从该位置开始删除后面所有值vm.items.splice(newLength)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
5. 生命周期函数
5.1 什么是生命周期(简述Vue的生命周期)
-
生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调;
-
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;
-
那么我们就可以在该生命周期中编写属于自己的逻辑代码了;
每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程,在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑(比如组件创建完后就请求一些服务器数据)
- 创建 create (beforeCreate / created)
- 挂载 mount ( beforeMount / mounted)
- 更新 update (beforeUpdate / updated)
- 卸载 unmount (beforeUnmount / unmounted)
常见提问:
- created表示完成数据观测、属性和方法的运算和初始化事件,此时$el属性还未显示出来
- DOM渲染在mounted中就已经完成了
一般在created里做网络请求,在mounted做数据挂载(mounted阶段才有真实的DOM)
5.2 生命周期相关题目
1)Vue 实例的 data 属性,可以在哪些生命周期中获取到?(B/C/D)
A. beforeCreate B. created C. beforeMount D. mounted
6. Vue中的diff算法
vue基于虚拟DOM做更新,diff又是其核心部分,因此常被问道,此题考查面试者深度。
-
定义diff
-
它的必要性
-
它在哪里被使用
-
它如何运作
-
提升:说一些细节
回答范例:
-
diff算法是虚拟DOM技术的产物,vue里面实际叫做patch,它的核心实现来自于snabbdom;通过新旧虚拟DOM作对比(即patch),将变化的地方转换为DOM操作
-
在vue 1中是没有patch的,因为界面中每个依赖都有专门的watcher负责更新,这样项目规模变大就会成为性能瓶颈,vue 2中为了降低watcher粒度,每个组件只有一个watcher,但是当需要更新的时候,怎样才能精确找到发生变化的地方?这就需要引入patch才行。
-
组件中数据发生变化时,对应的watcher会通知更新并执行其更新函数,它会执行渲染函数获取全新虚拟dom:newVnode,此时就会执行patch比对上次渲染结果oldVnode和新的渲染结果newVnode。
-
patch过程遵循深度优先、同层比较的策略;两个节点之间比较时,如果它们拥有子节点,会先比较子节点;比较两组子节点时,会假设头尾节点可能相同先做尝试,没有找到相同节点后才按照通用方式遍历查找;查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效。
7. 组件通信
7.1 父子组件通信
-
父传子:props
在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示
-
子传父:$emit事件
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容时,就要用到子传父。
-
除了父子组件之间的通信之外,还会有非父子组件之间的通信。
有不止两种方式: 1. provide/inject; 2. 全局事件总线bus
8. nextTick作用与实现原理
这道题考查对vue异步更新队列的理解,有一定深度。
答题思路:
- nextTick是啥?下一个定义
- 为什么需要它呢?用异步更新队列实现原理解释
- 在什么地方用它呢?
- 下面介绍一下如何使用nextTick
- 最后能说出源码实现就会显得你格外优秀
官方定义:
Vue.nextTick( [callback, context] )
用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据vm.msg = 'Hello'// DOM 还没有更新Vue.nextTick(function () {// DOM 更新了})
- 1
- 2
- 3
- 4
- 5
- 6
回答范例:
-
nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法
-
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。
-
所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法。
-
比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。
-
我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。
9. 全局导航钩子函数应用场景
router.beforeEach
(全局前置守卫)是页面加载之前,在每次路由改变的时候执行一遍。
router.afterEach
(全局后置守卫),与上相反,是页面加载之后。
应用场景:
- 可进行一些页面跳转前处理,例如判断需要登录的页面进行拦截,做登录跳转。
- 进入页面登录判断、管理员权限判断、浏览器判断
10. computed | watch 区别
computed 计算属性 :
依赖其它属性值,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值,如果和上次计算结果不一致,重新渲染页面。
watch 侦听器 :
更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
追问:computed 和 watch 应用场景?
computed:当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。(computed+缓存)
watch:当我们需要在数据变化时执行的操作时使用(如调用其它函数)
追问:能使用箭头函数定义computed和watch吗?
不能,因为箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,为undefined
11. 为什么在 vue 组件中,data 必须是一个函数?
在new Vue()实例中,data 可以是对象,但在 vue 组件中,data 必须是一个函数,确保多个组件实例之间是独立的数据。如果 data 是对象,组件实例之间就共享了。
关键词: 复用 + 污染 + 函数返回 + 数据拷贝
因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。
其他
1. public和assets文件的异同
1. 相同点
文件夹中的资源在html中使用都是可以的。
2. 不同点
-
public中的文件,是不会经过编译的,打包后会生成dist文件夹,public中的文件只是复制一遍。因此,public建议放一些外部第三方,自己的文件放在assets,别人的放public中。
-
若把图片放在assets和public中,html页面都可以使用,但是在动态绑定中,assets路径的图片会加载失败(因为webpack使用的是commenJS规范,必须使用require才可以。
-
使用assets下面的资源,在js中使用的话,路径要经过webpack中的file-loader编译,路径不能直接写。
-
使用public文件下面的资源,是不会被webpack处理的,它们会被直接复制到最终的打包目录下面,且必须使用绝对路径来引用这些文件。
-