# 1 请说一下响应式数据的了解?
响应式数据分为两种,对象和数组。
- 对象
对象外部通过defineReactive办法,该办法是应用Object.defineProperty将属性进行劫持(只会劫持曾经存在的属性)。
比方多层对象是通过递归来实现劫持。(Vue3 是用proxy来实现的)
- 数组
数组则是通过重写数组办法来实现。创立一个新的原型对象,该对象继承自Array.prototype,新对象在对数组的办法进行改写。
- 引申
(1)对象层级过深,性能就会差 (2)不须要响应数据的内容不要放到data中 (3) Object.freeze() 能够解冻数据
2 Vue如何检测数组变动?
Vue在observer数据阶段会判断如果是数组的话,则批改数组的原型,这样的话,前面对数组的任何操作都能够在劫持的过程中管制。
const arrayProto = Array.prototype//原生Array的原型export const arrayMethods = Object.create(arrayProto);[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { const original = arrayProto[method]//缓存元素数组原型 //这里重写了数组的几个原型办法 def(arrayMethods, method, function mutator () { //这里备份一份参数应该是从性能方面的思考 let i = arguments.length const args = new Array(i) while (i--) { args[i] = arguments[i] } const result = original.apply(this, args)//原始办法求值 const ob = this.__ob__//这里this.__ob__指向的是数据的Observer let inserted switch (method) { case 'push': inserted = args break case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result })})//定义属性function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true });}
*引申
在Vue中批改数组的索引和长度是无奈监控到的。须要通过以上7种变异办法批改数组才会触发数组对应的watcher进行更新。数组中如果是对象数据类型也会进行递归劫持。那如果想更改索引更新数据怎么办?能够通过Vue.$set()来进行解决 =》 外围外部用的是splice办法
3 Vue中模板编译原理?
1 先查找是否有render函数,没有的话看是否传入了template,如果传入了将template转化为render函数,如果仍没有,会回去el的outerHtml模板内容,将其转化成render函数。总之是转化成render函数。
2 字符串模板转化成为render函数的过程:用正则解析模板字符串生成AST语法树。将生成的ast语法树拼接成字符串,其中蕴含了 _c,_v,_s等办法形容元素的节点,文本节点以及变量。而后把拼接好的字符串通过new Function(with(this){return ${code}})转化成render函数
4 生命周期钩子是如何实现的?
1 通过Vue.mixin定义全局生命钩子,Vue.mixin可调用屡次,会定义不同的合并策略,生命周期钩子的合并策略就是为每个钩子函数创立一个数组,每次定义的钩子函数保留在对应的数组中,并最终挂载到Vue.$options 上
2 Vue实例初始化会将Vue.$options上的全局钩子函数和实例的钩子函数合并,挂载到options上,最初在实例的不同阶段 通过callHook函数从$options中取出对应的钩子函数数组,遍历数组,顺次执行生命周期函数。
- Vue的生命周期钩子就是回调函数而已,外围是一个公布订阅模式
5 Vue.mixin的应用场景和原理
Vue.mixin的作用就是抽离公共的业务逻辑,原理相似“对象的继承”,当组件初始化时会调用mergeOptions办法进行合并,采纳策略模式针对不同的属性进行合并。如果混入的数据和自身组件中的数据抵触,会采纳“就近准则”以组件的数据为准。
6 nextTick在哪里应用?原理是?
nextTick中的回调是在下次 DOM 更新循环完结之后执行的提早回调。在批改数据之后立刻应用这个办法,获取更新后的 DOM。原理就是异步办法(promise,mutationObserver,setImmediate,setTimeout)常常与事件环一起来问(宏工作和微工作)
7 Vue为什么须要虚构DOM?
Virtual DOM就是用js对象来形容实在DOM,是对实在DOM的形象,因为间接操作DOM性能低然而js层的操作效率高,能够将DOM操作转化成对象操作,最终通过diff算法比对差别进行更新DOM(缩小了对实在DOM的操作)。虚构DOM不依赖实在平台环境从而也能够实现跨平台。实质上就是在JS和DOM之间的一个缓存。
8 Vue中的diff原理
- 1.先比拟是否是雷同节点
- 2.雷同节点比拟属性,并复用老节点
- 3.比拟儿子节点,思考老节点和新节点儿子的状况
- 4.优化比拟:头头、尾尾、头尾、尾头
- 5.比对查找进行复用
9 既然Vue通过数据劫持能够进准探测数据变动,为什么还须要虚构DOM进行diff检测差别?
响应式数据变动,Vue的确能够在数据发生变化时,响应式零碎能够立即得悉。然而如果给每个属性都增加watcher用于更新的话,会产生大量的watcher从而升高性能。而且粒度过细也会导致更新不精准的问题,所以vue采纳了组件级的watcher配合diff来检测差别。这里能够在讲一下diff的原理
10 Vue中computed和watch的区别
computed和watch都是基于Watcher来实现的,别离是计算属性watcher和用户watcher。computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性办法不会从新执行(能够用模板渲染,取值的过程中不反对异步办法)watch则是监控值的变动,当值发生变化时调用对应的回调函数。computed不会立刻执行,外部通过defineProperty进行定义。并且通过dirty属性来检测依赖的数据是否发生变化。watch则是立刻执行将老值保留在watcher上,当数据更新时从新计算新值,将新值和老值传递到回调函数中。
11 Vue.set办法是如何实现的?
咱们给对象和数组自身都减少了dep属性。当给对象新增不存在的属性则触发对象依赖的watcher去更新,当批改数组索引时咱们调用数组自身的splice办法去更新数组
12 Vue的生命周期办法有哪些?个别在哪一步发动申请及起因
- beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
- created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算, watch/event 事件回调。这里没有$el
- beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。
- mounted el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用该钩子。
- beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁之前。
- updated 因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。
- destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
♥钩子函数的作用
- created 实例曾经创立实现,因为它是最早触发的起因能够进行一些数据,资源的申请。(服务端渲染反对created办法)
- mounted 实例曾经挂载实现,能够进行一些DOM操作
- beforeUpdate 能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
- updated 能够执行依赖于 DOM 的操作。然而在大多数状况下,你应该防止在此期间更改状态,因为这可能会导致更新有限循环。 该钩子在服务器端渲染期间不被调用。
- destroyed 能够执行一些优化操作,清空定时器,解除绑定事件
♥在哪发送申请都能够,次要看具体你要做什么事
13 vue-router有几种钩子函数?别离用在什么中央
1 全局钩子函数
- router.beforeEach
- router.beforeResolve (这和
router.beforeEach
相似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。 - router.afterEach
2 路由独享的守卫
beforeEnter
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] })
3 组件内的守卫
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创立 }, beforeRouteUpdate (to, from, next) { // 在以后路由扭转,然而该组件被复用时调用 // 举例来说,对于一个带有动静参数的门路 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 因为会渲染同样的 Foo 组件,因而组件实例会被复用。而这个钩子就会在这个状况下被调用。 // 能够拜访组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航来到该组件的对应路由时调用 // 能够拜访组件实例 `this` }}
残缺的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创立好的组件实例会作为回调函数的参数传入。
14 vue-router的两种模式的区别
hash模式
url地址前面 hash 值的变动,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash 值的变动,会触发hashchange
这个事件,通过这个事件咱们就能够晓得 hash 值产生了哪些变动。而后咱们便能够监听hashchange
来实现更新页面局部内容的操作:
hash模式背地的原理是onhashchange
事件,能够在window
对象上监听这个事件
history模式
因为HTML5规范公布,多了两个 API,pushState()
和 replaceState()。
通过这两个 API (1)能够扭转 url 地址且不会发送申请,(2)不仅能够读取历史记录栈,还能够对浏览器历史记录栈进行批改。
除此之外,还有popState().当浏览器跳转到新的状态时,将触发popState事件.
当刷新时,如果服务器中没有相应的响应或者资源,会呈现404