In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the relevant knowledge of "what is the asynchronous update mechanism of Vue and the principle of nextTick". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
1. Asynchronous update
In the setter accessor in the responsive method defineReactive that depends on the collection principle, there is an update dep.notify () method that notifies each watchers collected in dep's subs that subscribes to its own changes to execute the update. Let's look at the implementation of the update method:
/ / src/core/observer/watcher.js / * Subscriber interface, callback when dependency changes * / update () {if (this.computed) {/ / A computed watcher has two modes: activated lazy (default) / / set activated only if it is dependent by at least one subscriber This is usually the render function if of another calculation property or component (this.dep.subs.length = 0) {/ / if no one subscribes to the change / / lazy of this calculation property, we want it to perform the calculation only when necessary, so we simply mark the observer as dirty / / when the calculation property is accessed The actual calculation is executed in this.evaluate () this.dirty = true} else {/ / activated mode, we want to actively perform the calculation, but notify our subscribers this.getAndInvoke (() = > {this.dep.notify () / / notify the rendering watcher to re-render only if the value does change. Notification relies on all its own watcher to execute update})} elseif (this.sync) {/ / synchronous this.run ()} else {queueWatcher (this) / / asynchronously pushed to the dispatcher watcher queue and called on the next tick}}
If it is neither computed watcher nor sync, the current watcher calling update will be pushed to the dispatcher queue, and the next tick will be called. Take a look at queueWatcher:
/ / src/core/observer/scheduler.js / * put an observer object push into the observer queue, and the same id already exists in the queue * the watcher will be skipped unless it is pushed * / exportfunction queueWatcher (watcher: Watcher) {const id = watcher.id if (has [id] = = null) {/ / verify the existence of id when the queue is being flush. If it does not exist, mark the hash table has, which is used to verify has [id] = true queue.push (watcher) / / if it is not in flush. Push directly to the queue whether the if (! waiting) {/ / tag has been passed to nextTick waiting = true nextTick (flushSchedulerQueue)}} / * reset scheduler status * / function resetSchedulerState () {queue.length = 0 has = {} waiting = false}
Here, a hash map of has is used to check whether the id of the current watcher exists. If it already exists, skip it. If it does not exist, push it to the queue queue and mark the hash table has for the next check to prevent repeated additions. This is a process of removing duplicates, which is better than going to queue for civilization every time. The changes of the same watcher of patch will not be repeated during rendering, so that even if the data used in the view is modified synchronously a hundred times, the last modification will only be updated when asynchronous patch.
The waiting method here is used to mark whether the flushSchedulerQueue has been passed to the nextTick mark bit. If it has been passed, only push to the queue does not pass the flushSchedulerQueue to the nextTick. When the resetSchedulerState resets the state of the scheduler, the waiting will be set back to the false to allow the flushSchedulerQueue to be passed to the next tick callback. In short, the flushSchedulerQueue callback is only allowed to be passed once in a tick. Take a look at what the callback flushSchedulerQueue that was passed to nextTick did:
The callback function of / / src/core/observer/scheduler.js / * nextTick, which runs watchers * / function flushSchedulerQueue () {flushing = true let watcher, id queue.sort ((a, b) = > a.id-b.id) / / sort for (index = 0; index) when two queues are dropped on the next tick
< queue.length; index++) { // 不要将length进行缓存 watcher = queue[index] if (watcher.before) { // 如果watcher有before则执行 watcher.before() } id = watcher.id has[id] = null// 将has的标记删除 watcher.run() // 执行watcher if (process.env.NODE_ENV !== 'production' && has[id] != null) { // 在dev环境下检查是否进入死循环 circular[id] = (circular[id] || 0) + 1// 比如user watcher订阅自己的情况 if (circular[id] >MAX_UPDATE_COUNT) {/ / continuously executed watch a hundred times to indicate that there may be an endless loop warn () / / warning that there may be an endless loop}} resetSchedulerState () / / reset the dispatcher state callActivatedHooks () / / to set the subcomponent state to active and call the activated hook callUpdatedHooks () / / call the updated hook}
Execute the flushSchedulerQueue method in the nextTick method, which executes the run method of watcher in queue one by one. We see that at first, there is a queue.sort () method that sorts the watcher in the queue according to id, which ensures that:
Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community
The order in which the components are updated is from the parent component to the child component, because the parent component is always created before the child component.
A component's user watchers (listener watcher) runs before render watcher because user watchers tends to be created earlier than render watcher
If a component is destroyed while the parent component watcher is running, its watcher execution will be skipped
In the for loop in each execution queue, index
< queue.length 这里没有将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue。 那么数据的修改从model层反映到view的过程:数据更改 ->Setter-> Dep-> Watcher-> nextTick-> patch-> Update View
2. NextTick principle
2.1 Macro tasks / micro tasks
Let's take a look at what nextTick does to the method that contains each watcher execution after it is passed into nextTick as a callback. However, first of all, you need to understand the concepts of EventLoop, macro task and micro task in the browser. If you don't understand, you can refer to the article event Loop in JS and Node.js. Here is a diagram to show the execution relationship between the latter two in the main thread:
Macro task micro task
Explain that when the main thread finishes performing the synchronization task:
Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community
The engine first fetches the first task from the macrotask queue. After execution, it takes out all the tasks in the microtask queue and executes them sequentially.
Then take the next one from the macrotask queue, and when the execution is finished, take out all the microtask queue again
Cycle through until the tasks in both queue are taken.
The common types of asynchronous tasks in the browser environment, by priority:
Macro task: synchronization code, setImmediate, MessageChannel, setTimeout/setInterval
Micro task:Promise.then 、 MutationObserver
Some articles call micro task a micro task and macro task a macro task because the spelling of the two words is so similar. -, so the following comments are mostly expressed in Chinese ~
First, let's take a look at the implementation of micro task and macro task in the source code: macroTimerFunc, microTimerFunc
/ / src/core/util/next-tick.js const callbacks = [] / / stores the callback let pending = false// that is executed asynchronously. If timerFunc has been pushed to the task queue, there is no need to repeat the push / * synchronously execute the callback in callbacks one by one * / function flushCallbacks () {pending = false const copies = callbacks.slice (0) callbacks.length = 0 for (let I = 0; I)
< copies.length; i++) { copies[i]() } } let microTimerFunc // 微任务执行方法 let macroTimerFunc // 宏任务执行方法 let useMacroTask = false// 是否强制为宏任务,默认使用微任务 // 宏任务 if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { macroTimerFunc = () =>{setImmediate (flushCallbacks)}} elseif (typeof MessageChannel! = = 'undefined' & & (isNative (MessageChannel) | | MessageChannel.toString () =' [object MessageChannelConstructor]'/ / PhantomJS)) {const channel = new MessageChannel () const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () = > {port.postMessage (1)}} else {macroTimerFunc = () = > {setTimeout (flushCallbacks) 0)}} / / if (typeofPromise! = = 'undefined' & & isNative (Promise)) {const p = Promise.resolve () microTimerFunc = () = > {p.then (flushCallbacks)}} else {microTimerFunc = macroTimerFunc / / fallback to macro}
FlushCallbacks this method is to synchronously execute the callback functions in callbacks, and the callback functions in callbacks are added when calling nextTick; so how to use micro task and macro task to execute flushCallbacks? here their implementation macroTimerFunc and microTimerFunc use the API of macro / micro tasks in the browser to wrap the flushCallbacks method. For example, the macro task method macroTimerFunc= () = > {setImmediate (flushCallbacks)}, so that when the macro task is triggered, macroTimerFunc () can consume these callbacks saved in the callbacks array when the next macro task loop in the browser. At the same time, you can also see that the asynchronous callback function passed to nextTick is compressed into a synchronous task to be executed in a tick, rather than opening multiple asynchronous tasks.
Note that there is a difficult place to understand here. The first time nextTick is called, pending is false. By this time, you have already push to the task of a macro task or micro task in the browser event loop. If you continue to add to callbacks without flush dropping, the callback added later will be executed when executing this placeholder queue, so macroTimerFunc and microTimerFunc are equivalent to task queue placeholders. Later, if pending is true, you will continue to add to placeholder queue. Event loop will be executed when it is the task queue's turn. Pending is set to false when flushCallbacks is executed, which allows the next round of nextTick to occupy space to event loop.
You can see that the above macroTimerFunc and microTimerFunc have been steadily degraded or downgraded under different browser compatibility:
1. MacroTimerFunc: setImmediate-> MessageChannel-> setTimeout. First, check whether setImmediate is natively supported. This method is only natively implemented in IE and Edge browsers, and then check whether MessageChannel is supported. If you don't know about MessageChannel, you can refer to this article. If you don't support it, use setTimeout at last. Why do you give priority to using setImmediate and MessageChannel rather than using setTimeout directly? it is because HTML5 stipulates that the minimum delay for setTimeout execution is 4ms, while the nested timeout is represented as 10ms. In order to make the callback execute as soon as possible, the first two without minimum delay limit are obviously superior to setTimeout.
2. MicroTimerFunc:Promise.then-> macroTimerFunc. First, check whether Promise is supported, and if so, call the flushCallbacks method through Promise.then, otherwise it will degenerate to macroTimerFunc; after vue2.5, the microtask's smoothly degraded MutationObserver is deleted in nextTick for compatibility reasons.
2.2 nextTick implementation
Finally, let's take a look at how the nextTick method we usually use is implemented:
/ / src/core/util/next-tick.js exportfunction nextTick (cb?: Function, ctx?: Object) {let _ resolve callbacks.push (() = > {if (cb) {try {cb.call (ctx)} catch (e) {handleError (e, ctx) 'nextTick')} elseif (_ resolve) {_ resolve (ctx)}) if (! pending) {pending = true if (useMacroTask) {macroTimerFunc ()} else {microTimerFunc ()} if (! cb & & typeofPromise! = =' undefined') {returnnewPromise (resolve = > {_ resolve = Resolve})}} / * method of forcing the use of macrotask * / exportfunction withMacroTask (fn: Function): Function {return fn._withTask | (fn._withTask = function () {useMacroTask = true const res = fn.apply (null) Arguments) useMacroTask = false return res})}
NextTick is divided into three parts here. Let's take a look at it together.
1. First of all, nextTick wraps the incoming cb callback function with try-catch and pushes it into the callbacks array in an anonymous function. This is to prevent a single cb from executing an error that does not cause the entire JS thread to die. Each cb is wrapped to prevent these callback functions from affecting each other if the execution error does not affect each other. For example, the previous callback function can still be executed after the error is thrown.
two。 Then check the pending status, which has the same meaning as waiting in the queueWatcher introduced earlier. It is a tag bit. At first, false is set to true before entering the macroTimerFunc and microTimerFunc methods, so the next call to nextTick will not enter the macroTimerFunc and microTimerFunc methods. In these two methods, flushCallbacks asynchronously performs the tasks collected in the callbacks queue at the next macro/micro tick, while the flushCallbacks method sets pending to false at the beginning of execution. So the next time you call nextTick, you can start a new round of macroTimerFunc and microTimerFunc, which forms the event loop in vue.
3. Finally, check to see if cb is passed in, because nextTick also supports a commitment call: nextTick (). Then (() = > {}), so if you don't pass in cb, you directly return a Promise instance and pass resolve to _ resolve, so that the latter jumps to the method passed into then when we call it.
There is also an important comment in the next-tick.js file in the Vue source code, which is translated here:
In previous versions of vue2.5, nextTick was basically implemented based on micro task, but in some cases micro task has too high priority and may be triggered between successive sequential events (for example, # 4521) or even during event bubbling of the same event (# 6566). However, if all of them are changed to macro task, it will also have a performance impact on some scenes with redrawing and animation, such as issue # 6813. The workaround provided by post-vue2.5 versions is to use micro task by default, but to enforce the use of macro task when needed (for example, in event handlers attached to v-on).
Why the default priority to use micro task is to make use of its high priority feature to ensure that all the micro tasks in the queue are executed in one cycle.
The method of forcing macro task is that when the DOM event is bound, by default, the callback handler function call withMacroTask method will be wrapped with handler = withMacroTask (handler), which ensures that changes in the data state will be pushed to the macro task during the whole callback function execution. The above implementation is in the add method of src/platforms/web/runtime/modules/events.js. You can take a look at the specific code for yourself.
Just as I was writing this article, I wonder if someone asked a question: vue 2.4 is different from the version 2.5 @ input event. The reason for this problem is that the version of DOM event before 2.5 uses micro task, and then uses macro task.
< Vue.js 升级踩坑小记>Here is a way to add native events to the mounted hook using addEventListener, see CodePen.
3. An example
With all that said, let's take an example and execute it. See CodePen.
{{name}} change name new Vue ({el:'# app', data () {return {name: 'SHERlocked93'}}) Methods: {change () {const $name = this.$refs.name this.$nextTick (() = > console.log ('before setter:' + $name [XSS _ clean])) this.name = 'name change' console.log ('synchronization mode:' + this.$ refs.name [XSS _ clean]) setTimeout (() = > this.console ("setTimeout method: "+ this.$ refs.name [XSS _ clean]) this.$nextTick (() = > console.log (after 'setter:' + $name [XSS _ clean])) this.$nextTick (). Then () = > console.log ('Promise mode:' + $name [XSS _ name])})
Perform the following to see the results:
Synchronization mode: before SHERlocked93 setter: after SHERlocked93 setter: name change Promise mode: name change setTimeout mode: name change
Why is this the result? explain it:
1. Synchronous method: when the name in data is modified, the dep.notify in the setter of name will be triggered to notify the render watcher that depends on this data to update,update. It will pass the flushSchedulerQueue function to nextTick,render watcher when the flushSchedulerQueue function runs and watcher.run will go to diff-> patch to re-render the re-render view. In this process, it will re-rely on collection. This process is asynchronous. So when we print after we directly modify the name, the asynchronous changes have not been patch to the view, so the DOM element on the view is still the original content.
2. Before setter: why print the original content before setter? it is because when nextTick is called, the callback is push into the callbacks array one by one, and then the for loop is executed one by one, so it is similar to the concept of queue, first-in, first-out. After modifying the name, the trigger fills the render watcher into the schedulerQueue queue and passes its execution function flushSchedulerQueue to nextTick. At this time, there is already a pre-setter function in the callbacks queue. Because the cb is entered into the callbacks queue by push after the pre-setter function, then the first-in, first-out callback in callbacks executes the pre-setter function first, and the watcher.run of render watcher is not executed, so the print DOM element is still the original content.
3. After setter: the flushSchedulerQueue has been executed after setter, and the render watcher has already patch the changes to the view, so getting the DOM at this time is the changed content.
4. Promise mode: this function is executed in the same way as Promise.then, and the DOM has been changed.
5. SetTimeout method: finally execute the task of macro task, when the DOM has been changed.
Note that before executing the asynchronous task of the setter pre-function, the synchronous code has been executed, the asynchronous task has not been executed, all the $nextTick functions have been executed, and all the callbacks have been put into the callbacks queue by push to wait for execution, so when the setter pre-function is executed, the callbacks queue is like this: [setter pre-function, flushSchedulerQueue,setter post-function, Promise mode function], which is a micro task queue. Execute macro task setTimeout after execution, so print out the above result.
In addition, if there are setImmediate, MessageChannel and setTimeout/setInterval tasks in the macro task queue of the browser, they will be executed one by one in the above order according to the order in which they were added to the event loop. So if the browser supports MessageChannel and nextTick executes macroTimerFunc, then if there are both tasks added by nextTick and tasks of setTimeout type added by users themselves in macrotask queue, the tasks in nextTick will be executed first, because MessageChannel has a higher priority than setTimeout. SetImmediate is the same.
This is the end of the content of "what is the principle of Vue asynchronous update mechanism and nextTick". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.