闽公网安备 35020302035485号
this.a = 1 this.b = 2 this.c = 3 this.a = 5 this.a = 6想想嘛,根据 Vue 响应式原理,响应式数据在赋值时运行 setter,会触发视图更新,但是就像上面例子中连续赋值的场景可以说十分常见了,在 React 倒是可以写在一个 setState 里,但是 Vue 没有这种方法。总不能每次更新数据都立即给你刷新一次页面,如果每次赋值都跑一趟 render、patch,那整个页面不是卡得不行吗?解决这个问题的关键就是先把需要运行的更新函数都存入队列(而且,在加入队列时,相同的函数不会重复加入),再异步运行队列。
var circular = {} // 开发环境检测循环引用
var queue = [] // watcher 队列
var activatedChildren = [] // 堆代码 duidaima.com
var has = {} // 是否已经加入队列
var waiting = false // 可以理解为是否在等待 flushing
var flushing = false // 是否已经开始处理队列
var index = 0 // 队列当前运行到哪里
你也许十分好奇,为什么会有 waiting 和 flushing 这么相似的两个变量呢?开始我也很疑惑,是否正在 flushing,是否等待 flushing(waiting),虽然还是有细微的区别:2.在 flushing 时,已经确定开始队列任务
function queueWatcher(watcher) {
var id = watcher.id
if (has[id] == null) {
has[id] = true // 标记 watcher 已添加到队列
if (!flushing) {
// 队列还没开始更新,直接推入队列即可
queue.push(watcher)
} else {
// 队列正在处理中时,Vue 的做法是直接把新的 watcher 插到运行到的最新位置
// 这蕴含着隐藏逻辑
// 已经在前面运行过的 watcher 这时候会立即再在下一个运行
var i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
最后的 nextTick(flushSchedulerQueue) 就是把运行 watcher 队列放入 nextTick 队列中(虽然没有传参,但是 flushSchedulerQueue 可以读取 queueWatcher 处理的 queue)。短短的这一句函数调用,连接了两个重点:flushSchedulerQueue 是处理 watcher 队列的核心,而 nextTick 是 Vue 异步渲染的核心。function flushSchedulerQueue() {
flushing = true
var watcher, id
queue.sort(function(a, b) {
return a.id - b.id
})
// queue 可变长,queue.length 不能缓存
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
id = watcher.id
has[id] = null // 已经处理的 watcher 从 has 去除
watcher.run()
}
// keep copies of post queues before resetting state
var activatedQueue = activatedChildren.slice()
var updatedQueue = queue.slice()
resetSchedulerState() // 重置队列相关变量
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
}
简单来说 flushSchedulerQueue 就是用于处理 queueWatcher 编排好的 queue。这里提出一个简单的实践问题,在 watch 某个值的回调函数中,可以访问最新的 DOM 吗?var nextTick = (function() {
var callbacks = []
var pending = false
var timerFunc
function nextTickHandler() {
pending = false
var copies = callbacks.slice(0)
callbacks.length = 0
for (var i = 0; i < copies.length; i++) {
copies[i]()
}
}
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = function() {
setImmediate(nextTickHandler)
}
} else if (
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
var channel = new MessageChannel()
var port = channel.port2
channel.port1.onmessage = nextTickHandler
timerFunc = function() {
port.postMessage(1)
}
} else if (typeof Promise !== 'undefined' && isNative(Promise)) {
// use microtask in non-DOM environments, e.g. Weex
var p = Promise.resolve()
timerFunc = function() {
p.then(nextTickHandler)
}
} else {
// fallback to setTimeout
timerFunc = function() {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick(cb, ctx) {
var _resolve
callbacks.push(function() {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function(resolve, reject) {
_resolve = resolve
})
}
}
})()
先解释一下 Vue 制造异步运行的方法吧——Vue.prototype.$nextTick = function(fn) {
return nextTick(fn, this)
}
2.nextTick 的 callbacks