共计 5076 个字符,预计需要花费 13 分钟才能阅读完成。
# 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