Vue2面试题常问面试题
对MVVM的理解:
MVVM 由 Model、View、ViewModel 三部分构成,Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来;ViewModel 是一个同步View 和 Model的对象。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
Vue数据双向绑定的原理:
实现mvvm的数据双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来给各个属性添加setter,getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
Vue响应式原理
Object.defineProperty 为对象中的每一个属性,设置get和set方法,每个声明的属性,都会有一个专属的依赖收集器 subs,当页面使用到某个属性时,触发ObjectdefineProperty get函数,页面的watcher就会被放到属性的依赖收集器subs中,在数据变化时,通知更新;
当数据改变的时候,会触发Object.defineProperty - set函数,数据会遍历自己的 依赖收集器 subs,逐个通知 watcher,视图开始更新;
1 | let obj = { |
vue中组件的data为什么是一个函数?而new Vue 实例里,data 可以直接是一个对象
Vue的data数据其实是Vue原型上的属性,数据存在于内存当中。Vue为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。因为使用对象的话,每个实例(组件)上使用的data数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。使用函数后,使用的是data()函数,data()函数中的this指向的是当前实例本身,就不会相互影响了。而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
vue中created与mounted区别
在created阶段,实例已经被初始化,但是还没有挂载至el上,所以我们无法获取到对应的节点,但是此时我们是可以获取到vue中data与methods中的数据的;
在mounted阶段,vue的template成功挂载在$el中,此时一个完整的页面已经能够显示在浏览器中,所以在这个阶段,可以调用节点了;
Vue中computed与method的区别
相同点:
如果作为模板的数据显示,二者能实现响应的功能,唯一不同的是methods定义的方法需要执行
不同点:
- 1.computed 会基于响应数据缓存,methods不会缓存;
- 2.diff之前先看data里的数据是否发生变化,如果没有变化computed的方法不会执行,但methods里的方法会执行
- 3.computed是属性调用,而methods是函数调用
虚拟DOM中key的作用
简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
复杂的说:当状态中的数据发生了变化时,vue会根据【新数据】生成【新的虚拟DOM】,随后
进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
旧虚拟DOM中找到了与新虚拟DOM相同的key
- 1.若虚拟DOM中的内容没有变,直接使用之前的真是DOM
- 2.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 3.旧虚拟DOM中未找到与新虚拟DOM相同的key
- 4.根据数据创建新的真实DOM,随后渲染到页面
用index作为key可能会引发的问题
若对数据进行:逆序添加/逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新,界面效果虽然没有问题,但是数据过多的话,会效率过低;
如果结构中还包含输入类的DOM,会产生错误DOM更新,界面有问题;
注意!如果不存在对数据的逆序操作,仅用于渲染表用于展示,使用index作为key是没有问题的。
Vue中watch用法详解
在vue中,使用watch来监听数据的变化;
- 1.监听的数据后面可以写成对象形式,包含handler方法,immediate和deep。
- 2.immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
- 3.当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
vue中对mixins的理解和使用
将组件的公共逻辑或者配置提取出来,哪个组件需要用到时,直接将提取的这部分混入到组件内部即可。这样既可以减少代码冗余度,也可以让后期维护起来更加容易。
这里需要注意的是:提取的是逻辑或配置,而不是HTML代码和CSS代码。其实大家也可以换一种想法,mixin就是组件中的组件,Vue组件化让我们的代码复用性更高,那么组件与组件之间还有重复部分,我们使用Mixin在抽离一遍。
可以理解为minins就是组件的组件 让可复用的组件提取出来供大家使用
定义mixin也非常简单,它就是一个对象而已,只不过这个对象里面可以包含Vue组件中的一些常见配置,如data、methods、created等等。然后引入就可以了 并且各个组件引入是独立的,A组件修改了mixin的值并不会影响B组件的mixin
vue中的插槽
定义:
插槽的使用过程其实是抽取共性、保留不同;
我们会将共同的元素、内容依然在组件内进行封装;
同时会将不同的元素使用slot作为占位,让外部决定到底显示什么样的元素;
使用方法:
- Vue中将
元素作为承载分发内容的出口; - 在封装组件中,使用特殊的元素
就可以为封装组件开启一个插槽; - 该插槽插入什么内容取决于父组件如何使用;
默认插槽:
当然这个默认的内容只会在没有提供插入的内容时,才会显示;
具名插槽:
当有多个插槽,并且想要插槽对应的显示使用具名插槽;
作用域
那就是在父组件中访问子组件的数据,或者从数据流向的角度来讲就是将子组件的数据传递到父组件。
为何Vue采用异步渲染
vue是组件级更新,组件内有数据变化时,该组件就会更新。例:this.a = 1、this.b=2(同一个watcher)
- 原因:如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染。所以为了性能考虑,Vue 会在本轮数据更新后,再去异步更新视图。而不是每当有数据更新,就立即更新视图。
过程:
- Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 中 观察到数据变化的 watcher 推送进这个队列。
- 如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据,避免不必要的计算和Dom操作。
- 而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
源码解析:
- 数据变化时,通过notify通知watcher进行更新操作;
- 通过subs[i].update依次调用watcher的update(未更新视图);
- 将watcher放到队列中,在queueWatcher会根据watcher的id进行去重(多个属性依赖一个watcher),如果队列中没有该watcher就会将该watcher添加到队列中(未更新视图);
- 通过nextTick异步执行flushSchedulerQueue方法刷新watcher队列(更新视图);
Vue 的异步更新机制是如何实现的?
Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的,首选微任务队列,宏任务队列次之。
当响应式数据更新后,会调用 dep.notify 方法,通知 dep 中收集的 watcher 去执行 update 方法,watcher.update 将 watcher 自己放入一个 watcher 队列(全局的 queue 数组)。
然后通过 nextTick 方法将一个刷新 watcher 队列的方法(flushSchedulerQueue)放入一个全局的 callbacks 数组中。
如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数,则执行 timerFunc 函数,将 flushCallbacks 函数放入异步任务队列。如果异步任务队列中已经存在 flushCallbacks 函数,等待其执行完成以后再放入下一个 flushCallbacks 函数。
flushCallbacks 函数负责执行 callbacks 数组中的所有 flushSchedulerQueue 函数。
flushSchedulerQueue 函数负责刷新 watcher 队列,即执行 queue 数组中每一个 watcher 的 run 方法,从而进入更新阶段,比如执行组件更新函数或者执行用户 watch 的回调函数。
$nextTick的理解
用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
为什么?
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
所以为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
使用场景
在你更新完数据后,需要及时操作渲染好的 DOM时
Vue除了核心功能默认内置的指令 ,Vue 也允许注册自定义指令。
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
添加自定义指令的两种方式:
全局指令: 通过 Vue.directive() 函数注册一个全局的指令。
局部指令:通过组件的 directives 属性,对该组件添加一个局部的指令。
使用场景:
需求:有些网站图片的加载做得非常优雅,在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来。用自定义指令可以非常方便的实现这个功能。
1 | <div id="app" v-image = "item " v-for="item in imageList"></div> |
Vue.set 改变数组和对象中的属性
在一个组件实例中,只有在data里初始化的数据才是响应的,Vue不能检测到对象属性的添加或删除,没有在data里声明的属性不是响应的,所以数据改变了但是不会在页面渲染;
解决办法:
使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
说说vue的生命周期的理解
vue通信方式:
- 1、父组件向子组件传递数据 props
- 2.子组件向父组件传递数据($emit的用法)
- 3.兄弟组件通信(通过父组件parent)
- 4、ref / $refs
- 5、eventBus事件总线($emit / $on)
- 6、依赖注入(provide / inject)
- 7.$parent / $children
- 10.vuex