Vue源码解读系列篇javascript
相信你们都知道,Vue
能够作到数据驱动视图更新,好比咱们就简单写一个事件以下:html
methods: {
tap() {
for (let i = 0; i < 10; i++) {
this.a = i;
}
this.b = 666;
},
},
复制代码
当咱们触发这个事件,视图中的a
和b
确定会发现一些变化。vue
那咱们思考一下,Vue
是如何管理这个变化的过程?好比上面这个案例,a
被循环了10次,那Vue
会去渲染视图10次吗?显然不会,毕竟这个性能代价很是大。毕竟咱们只须要a
最后一次的赋值。java
实际上Vue
是异步更新视图的,也就是说会等这个tap
事件执行完,检查发现只须要更新a
和b
,而后再一次性更新,避免无效的更新。node
Vue
官方文档也印证了咱们的想法,以下:react
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的全部数据变动。若是同一个 watcher 被屡次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免没必要要的计算和 DOM 操做是很是重要的。git
以上能够详见Vue官方文档 - 异步更新队列。github
Vue
通知视图更新,是经过dep.notify
,相信你读到这里确定是了解Vue
响应式原理的。那么来查看下dep.notify
都作了什么?耐心点,离真相愈来愈近了。面试
// dep.js
notify () {
const subs = this.subs.slice();
// 循环通知全部watcher更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
复制代码
首先循环通知全部watcher
更新,咱们发现watcher
执行了update
方法。浏览器
// watcher.js
update () {
if (this.lazy) {
// 若是是计算属性
this.dirty = true
} else if (this.sync) {
// 若是要同步更新
this.run()
} else {
// 进入更新队列
queueWatcher(this)
}
}
复制代码
update
方法首先判断是否是计算属性或开发者定义了同步更新,这些咱们先不看,直接进入正题,进入异步队列方法queueWatcher
。
那么再来看下queueWatcher
,我省略了绝大部分代码,毕竟代码是枯燥的,为了方便你们理解,都是一些思路性代码。
export function queueWatcher (watcher: Watcher) {
// 获取watcherid
const id = watcher.id
if (has[id] == null) {
// 保证只有一个watcher,避免重复
has[id] = true
// 推入等待执行的队列
queue.push(watcher)
// ...省略细节代码
}
// 将全部更新动做放入nextTick中,推入到异步队列
nextTick(flushSchedulerQueue)
}
function flushSchedulerQueue () {
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.run()
// ...省略细节代码
}
}
复制代码
经过上述代码能够看出咱们将全部要更新的watcher
队列放入了nextTick
中。 nextTick
的官方解读为:
在下次 DOM 更新循环结束以后执行延迟回调。在修改数据以后当即使用这个方法,获取更新后的 DOM。
这里的描述其实限制了nextTick
的技能,实际上nextTick
就是一个异步方法,也许和你使用的setTimeout
没有太大的区别。
那来看下nextTick
的源码究竟作了什么?
nextTick
源码不多,翻来翻去没几行,可是我也不打算展开讲,由于看代码真的很枯燥。 下面的代码只有几行,其实你能够选择跳过看结论。
// timerFunc就是nextTick传进来的回调等... 细节不展开
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
// 当原生 Promise 不可用时,timerFunc 使用原生 MutationObserver
// MutationObserver不要在乎它的功能,其实就是个能够达到微任务效果的备胎
)) {
timerFunc = () => {
// 使用 MutationObserver
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 若是原生 setImmediate 可用,timerFunc 使用原生 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最后的倔强,timerFunc 使用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
复制代码
总结就是Promise
> MutationObserver
> setImmediate
> setTimeout
。
果真和setTimeout
没有太大的区别~
再总结一下优先级:microtask (jobs)
优先。
nextTick
源码为何要microtask
优先?再理解这个问题答案以前,咱们还要复习eventLoop
知识。
用2张图带你们简单回忆一下,可是就不细讲了,你们能够自行查找资料。
setTimeout
、onClick
等等的一些操做,咱们会将他的执行结果放入队列,此期间主线程不阻塞。event loop
在队列里面从头开始取,在执行栈中执行 event loop
永远不会断。Event Loop
(事件循环机制)。microtask
和宏任务tasks
队列microtask
都执行完毕,注意是全部的,他才会从宏任务tasks
队列中取事件。event loop
还会继续循环,他会再去微任务microtask
执行全部的任务,而后再从宏任务tasks
队列里面取一个,如此反复循环。简单的回忆了eventLoop
、微任务、宏任务后,咱们还要再抛出一个结论。
咱们发现,原来在执行微任务以后还会执行渲染操做!!!(固然并非每次都会,但至少顺序咱们是能够确定的)。
event loop
中屡次修改同一dom
,只有最后一次会进行绘制。Update the rendering
)会在event loop
中的tasks
和microtasks
完成后进行,但并非每轮event loop
都会更新渲染,这取决因而否修改了dom
和浏览器以为是否有必要在此时当即将新状态呈现给用户。若是在一帧的时间内(时间并不肯定,由于浏览器每秒的帧数总在波动,16.7ms只是估算并不许确)修改了多处dom
,浏览器可能将变更积攒起来,只进行一次绘制,这是合理的。event loop
都即时呈现变更,可使用requestAnimationFrame
。这里我抛出结论,缘由和理论知识能够看这篇文章 从event loop规范探究javaScript异步及浏览器更新渲染时机 ,这位大神写的很好。
不知道你们有没有猜出【nextTick
为何要尽量的microtask
优先?】 这里又盗了大神的图,event loop
的大体循环过程:
假设如今执行到某个 task,咱们对批量的dom
进行异步修改,咱们将此任务插进tasks
,也就是用宏任务实现。
显而易见,这种状况下若是task
里排队的队列比较多,同时遇到屡次的微任务队列执行完。那颇有可能触发屡次浏览器渲染,可是依旧没有执行咱们真正的修改dom
任务。
这种状况,不只会延迟视图更新,带来性能问题。还有可能致使视图上一些诡异的问题。 所以,此任务插进microtasks
:
task
队列若是有大量的任务等待执行时,将
dom
的变更做为
microtasks
而不是宏任务(
task
)能更快的将变化呈现给用户。
之因此讲这篇文章,是由于在最近在读Vue的源码,我看的是2.6.10, 发现nextTick和2.5版本的实现不太同样。你们能够看下这位大佬的文章 Vue.js 升级踩坑小记
文章内容基本都是在其余大佬的基础上进行的理解,讲错的你们能够批评指正~
文章有一些结论直接参考其余文章,本身实在是懒得写啦~~
侵权删 ^^