The source code of this api has been read a long time ago, but there has been no summary, so here is a summary.
How is this api used
nextTick
in Vue. One is used as a global method Vue.nextTick
, and the other is mounted on a component instance and used by vm.$nextTick
. When called as an instance method, the callback this
automatically bound to the instance that called it.
parameter:
// {Function} [callback]
// {Object} [context]
Vue.nextTick([callback, context])
usage:
The delayed callback is executed after the next DOM update cycle ends. Use this method immediately after modifying the data to get the updated DOM.
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
New since 2.1.0: If no callback is provided and in an environment that supports Promise, a Promise will be returned.
// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})
Source code analysis
Vue.nextTick
is in this directory:
node_modules/vue/src/core/util/next-tick.js
The code here is recommended to start at line 33:
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
The logic of this part is very simple, that is, environment , and then checks Promise
-> MutationObserver
-> setImmediate
-> setTimeout
exists, and if it exists, use it to determine which API the callback function queue is asynchronous with. implement.
Then look at the method definition nextTick
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
In the above code, it is slightly more complicated because it needs to implement the new logic of returning Promise. You can directly look at the simplified logic below:
export function nextTick (cb?: Function, ctx?: Object) {
callbacks.push(() => {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
})
if (!pending) {
pending = true
timerFunc()
}
}
When the nextTick
function receives a callback function, it does not call it first, but pushes it to a global callbacks
array. In nextTick
Finally, a method called timerFunc
method, which is actually just sniff the environment when selected api, to Promise
for example, timerFunc
method should be long like this:
const p = Promise.resolve()
let timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
After calling timerFunc
, the flushCallbacks
method will be executed in the next round of event loop. Let's look at the flushCallbacks
method definition:
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
flushCallbacks
does is very simple. It executes all the methods in callbacks
callbacks
.
Attentive students here may ask, nextTick
is the pending
of 06103643993349 in 06103643993348. At first, it was false
, then when calling nextTick
, it was changed to true
, and finally when flushCallbacks
executed, it was changed to false
, which seemed useless at first glance. . In fact, otherwise, we know that the nextTick
method can be called multiple times in a synchronous method:
export default {
methods: {
handleClick() {
this.$nextTick(() => {
console.log(233);
})
this.$nextTick(() => {
console.log(666);
})
}
}
}
In the above code, the first nextTick
called, the callback function is pushed into the callbacks
array, and then timerFunc
called to join the asynchronous queue. When the second nextTick
called, the callback function is pushed into the callbacks
array, because at this time pending
already true
, so timerFunc
will not be executed repeatedly, that is, the asynchronous queue will not be added repeatedly. Only in the next round of event loop, after the functions in the asynchronous queue are executed, pending
becomes false
, then the asynchronous queue can be added again.
So to summarize, when the nextTick
function receives a callback function, it does not call it first, but puts it in a global queue
queue, and waits for the next round of the event loop to queue
functions in sequence.
This queue may be the microTask
queue, or it may be the macroTask
queue. Promise
and MutationObserver
belong to the micro task queue, and setImmediate
and setTimeout
belong to the macro task queue.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。