官网的描述有这么一句话
Vue.js 默认异步更新 DOM。每当观察到数据变化时,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中。等到下一次事件循环,Vue 将清空队列,只进行必要的 DOM 更新。在内部异步队列优先使用 MutationObserver,如果不支持则使用 setTimeout(fn, 0)。
里面提到同一事件循环
不是太理解,所谓的同一是啥?
官网的描述有这么一句话
Vue.js 默认异步更新 DOM。每当观察到数据变化时,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中。等到下一次事件循环,Vue 将清空队列,只进行必要的 DOM 更新。在内部异步队列优先使用 MutationObserver,如果不支持则使用 setTimeout(fn, 0)。
里面提到同一事件循环
不是太理解,所谓的同一是啥?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="demo">
<input v-model="text" />
<a @click="change()">change</a>
</div>
<script src="http://cdn.bootcss.com/vue/1.0.17/vue.min.js"></script>
<script>
'use strict';
var vm = new Vue({
el: '#demo',
data() {
return {
text: '',
}
},
methods: {
change() {
this.text = '123'
this.text = '234'
this.text = '345'
this.$nextTick(function () {
this.text = '456'
})
}
},
watch: {
'text'(n, o) {
console.log(n, o)
}
}
})
</script>
</body>
</html>
看源码
#batcher.js
import config from './config'
import {
warn,
nextTick,
devtools
} from './util/index'
// we have two separate queues: one for directive updates
// and one for user watcher registered via $watch().
// we want to guarantee directive updates to be called
// before user watchers so that when user watchers are
// triggered, the DOM would have already been in updated
// state.
var queue = [] //这个应该用于指令异步更新的队列
var userQueue = [] //这个应该是用于用户定义的data异步更新的队列
var has = {}
var circular = {}
var waiting = false
/**
* Reset the batcher's state.
*/
// 重置队列状态
function resetBatcherState () {
queue.length = 0
userQueue.length = 0
has = {}
circular = {}
waiting = false
}
/**
* Flush both queues and run the watchers.
*/
//执行这2个队列里的异步更新
function flushBatcherQueue () {
runBatcherQueue(queue)
runBatcherQueue(userQueue)
// user watchers triggered more watchers,
// keep flushing until it depletes
if (queue.length) { //如果执行的时候又有异步更新被查到队列中,就继续执行
return flushBatcherQueue()
}
// dev tool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
resetBatcherState() 执行完后重置异步更新队列
}
/**
* Run the watchers in a single queue.
*
* @param {Array} queue
*/
//这里就是循环更新异步队列中的watcher
function runBatcherQueue (queue) {
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (let i = 0; i < queue.length; i++) {
var watcher = queue[i]
var id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > config._maxUpdateCount) {
warn(
'You may have an infinite update loop for watcher ' +
'with expression "' + watcher.expression + '"',
watcher.vm
)
break
}
}
}
queue.length = 0
}
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*
* @param {Watcher} watcher
* properties:
* - {Number} id
* - {Function} run
*/
//这个开放的接口,把需要更新的watcher添加到异步队列中
export function pushWatcher (watcher) {
const id = watcher.id
if (has[id] == null) {
// push watcher into appropriate queue
const q = watcher.user
? userQueue
: queue
has[id] = q.length
q.push(watcher)
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushBatcherQueue)
}
}
}
你的问题在于对事件循环不理解,其实和vue
的关系不大,那也不是尤小右发明的,而是Javascript
的特性
关于事件循环,文档给你:Event-Loop,针对文中一个例子,我们现在来聊下“同一事件循环内”是个什么鬼:
如果你执行一下这段代码,你会发现输出结果并没有按顺序来,而是第3
、9
、15
行先顺序输出,然后才是6
、12
行。But why?因为:
理解了事件循环本身是什么东西,vue
文档里的解释再看看
9 回答1.8k 阅读✓ 已解决
6 回答1.8k 阅读
3 回答1.5k 阅读✓ 已解决
4 回答1.4k 阅读✓ 已解决
3 回答1.3k 阅读
2 回答1.3k 阅读✓ 已解决
3 回答1.5k 阅读✓ 已解决
首先,理解事件循环是什么,我们才知道同一事件循环是指啥。
一般来讲,任务队列分为macro-task和micro-task,这两者是什么自己百度。事件循环会执行任务队列中的任务,当执行任务队列时,会先执行一个macro-task,再执行所有的micro-task,然后浏览器根据情况判断是否渲染,这是一次完整的事件循环。然后会再执行一个macro-task,执行此时所有的micro-task,再渲染,又是一次事件循环,一直重复。
那么,同一事件循环,一般指的是在macro-task执行时所发生的数据变更,因为vue异步更新用到了nextTick,这个方法内部就是调用了js异步方法的回调,比如MutationObserver的回调,或者setTimeout的回调,MutationObserver属于micro-task,setTimeout属于macro-task。
所以,如果异步更新是在MutationObserver(有的浏览器不支持,此时会用setTimeout)中,那么更新就是在此次macro-task执行完之后的micro-task,因为MutationObserver属于micro-task。这个时候,数据还都同在一次事件循环中,而且是本次事件循环还没结束。
如果是setTimeout,因为setTimeout属于macro-task,其实就是下一次事件循环了,因为一次事件循环只执行一个macro-task(这个有待商榷,不同浏览器好像不同,我们暂且认为一次循环只执行一个),那么更新就是在下一次事件循环了,这个时候的数据,也都是一次事件循环的数据,只不过是上一次的。人家的描述没毛病,是同一事件循环内的。
描述的可能有点乱,没听过这些名词的话可能还有些懵,建议先去了解我说的这些名词,再来看,或者网上也有很多说明这些东西的文章了。