Vue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析
この記事では、Vue ソース コードのバッチ非同期更新と nextTick 原則の分析を紹介します。必要な方は参考にしてください。
vue はすでに国内のフロントエンド Web エンドの 3 分の 1 を占めており、私も日常的に使用している主要なテクノロジー スタックの 1 つであり、その理由についても興味があります。このような記事については、この機会に皆さんの記事やディスカッションからいくつかのヒントを得ると同時に、ソースコードを読んだときにいくつかの考えをまとめて、いくつかの記事を作成します。私自身の考え方の要約
ターゲット Vue バージョン: 2.5 .17-beta.0
2.5.17-beta.0
vue源码注释:https://github.com/SHERlocked93/vue-analysis
声明:文章中源码的语法都使用 Flow,并且源码根据需要都有删节(为了不被迷糊 @_@),如果要看完整版的请进入上面的github地址,本文是系列文章,文章地址见底部~
1. 异步更新
我们在依赖收集原理的响应式化方法 defineReactive
中的 setter
访问器中有派发更新 dep.notify()
方法,这个方法会挨个通知在 dep
的 subs
中收集的订阅自己变动的watchers执行update。一起来看看 update
方法的实现:
// src/core/observer/watcher.js /* Subscriber接口,当依赖发生改变的时候进行回调 */ update() { if (this.computed) { // 一个computed watcher有两种模式:activated lazy(默认) // 只有当它被至少一个订阅者依赖时才置activated,这通常是另一个计算属性或组件的render function if (this.dep.subs.length === 0) { // 如果没人订阅这个计算属性的变化 // lazy时,我们希望它只在必要时执行计算,所以我们只是简单地将观察者标记为dirty // 当计算属性被访问时,实际的计算在this.evaluate()中执行 this.dirty = true } else { // activated模式下,我们希望主动执行计算,但只有当值确实发生变化时才通知我们的订阅者 this.getAndInvoke(() => { this.dep.notify() // 通知渲染watcher重新渲染,通知依赖自己的所有watcher执行update }) } } else if (this.sync) { // 同步 this.run() } else { queueWatcher(this) // 异步推送到调度者观察者队列中,下一个tick时调用 } }
如果不是 computed watcher
也非 sync
会把调用update的当前watcher推送到调度者队列中,下一个tick时调用,看看 queueWatcher
:
// src/core/observer/scheduler.js /* 将一个观察者对象push进观察者队列,在队列中已经存在相同的id则 * 该watcher将被跳过,除非它是在队列正被flush时推送 */ export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { // 检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验 has[id] = true queue.push(watcher) // 如果没有正在flush,直接push到队列中 if (!waiting) { // 标记是否已传给nextTick waiting = true nextTick(flushSchedulerQueue) } } } /* 重置调度者状态 */ function resetSchedulerState () { queue.length = 0 has = {} waiting = false }
这里使用了一个 has
的哈希map用来检查是否当前watcher的id是否存在,若已存在则跳过,不存在则就push到 queue
队列中并标记哈希表has,用于下次检验,防止重复添加。这就是一个去重的过程,比每次查重都要去queue中找要文明,在渲染的时候就不会重复 patch
相同watcher的变化,这样就算同步修改了一百次视图中用到的data,异步 patch
的时候也只会更新最后一次修改。
这里的 waiting
方法是用来标记 flushSchedulerQueue
是否已经传递给 nextTick
的标记位,如果已经传递则只push到队列中不传递 flushSchedulerQueue
给 nextTick
,等到 resetSchedulerState
重置调度者状态的时候 waiting
会被置回 false
允许 flushSchedulerQueue
被传递给下一个tick的回调,总之保证了 flushSchedulerQueue
回调在一个tick内只允许被传入一次。来看看被传递给 nextTick
的回调 flushSchedulerQueue
做了什么:
// src/core/observer/scheduler.js /* nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers */ function flushSchedulerQueue () { flushing = true let watcher, id queue.sort((a, b) => a.id - b.id) // 排序 for (index = 0; index MAX_UPDATE_COUNT) { // 持续执行了一百次watch代表可能存在死循环 warn() // 进入死循环的警告 break } } } resetSchedulerState() // 重置调度者状态 callActivatedHooks() // 使子组件状态都置成active同时调用activated钩子 callUpdatedHooks() // 调用updated钩子 }
在 nextTick
方法中执行 flushSchedulerQueue
方法,这个方法挨个执行 queue
中的watcher的 run
方法。我们看到在首先有个 queue.sort()
方法把队列中的watcher按id从小到大排了个序,这样做可以保证:
组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
一个组件的user watchers(侦听器watcher)比render watcher先运行,因为user watchers往往比render watcher更早创建
如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过
在挨个执行队列中的for循环中,index 这里没有将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue。
那么数据的修改从model层反映到view的过程:数据更改 -> setter -> Dep -> Watcher -> nextTick -> patch -> 更新视图
2. nextTick原理
2.1 宏任务/微任务
这里就来看看包含着每个watcher执行的方法被作为回调传入 nextTick
之后,nextTick
对这个方法做了什么。不过首先要了解一下浏览器中的 EventLoop
、macro task
、micro task
ステートメント:記事内のソース コードの構文は Flow を使用しており、ソース コードは必要に応じて省略されています (混乱しないように@_@)。完全版をご覧になりたい場合は、上記の github アドレスを入力してください。一連の記事は下部の記事アドレスを参照してください~
1. 非同期更新
応答メソッドdefineReactive の原理における <code>setter
アクセサに依存しています。 にはディスパッチ更新 dep.notify()
メソッドがあり、このメソッドは dep の <code>subs
に収集されたウォッチャーに 1 つずつ通知します。独自の変更をサブスクライブする code> は更新を実行します。 update
メソッドの実装を見てみましょう:
// src/core/util/next-tick.js const callbacks = [] // 存放异步执行的回调 let pending = false // 一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送 /* 挨个同步执行callbacks中回调 */ function flushCallbacks() { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i { setImmediate(flushCallbacks) } } else if (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 (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { microTimerFunc = macroTimerFunc // fallback to macro }
computed watcher
または sync
ではない場合、呼び出している現在のウォッチャー更新はスケジューラにプッシュされます。キューでは、次のティックで呼び出されます。queueWatcher
を見てください: - ここでは
has
のハッシュ マップが使用されています。現在のウォッチャーの ID が存在するかどうかを確認し、すでに存在する場合はスキップし、存在しない場合はqueue
キューにプッシュし、ハッシュ テーブルにマークを付けます。繰り返しの追加を防ぐために次の検査を行います。これは、重複をチェックするために毎回キューにアクセスするよりも効率的なプロセスであり、この方法では、同じウォッチャーに対するpatch
の変更が繰り返されなくなります。 100 件の変更が同時に行われた場合でも、セカンダリ ビューで使用されるデータは、非同期patch
中の最後の変更でのみ更新されます。 ここでのwaiting
メソッドは、flushSchedulerQueue
がnextTick
のタグ ビットに渡されたかどうかをマークするために使用されます。flushSchedulerQueue
をnextTick
に渡さないと、resetSchedulerStatewaiting
がに戻されます。 /code> はスケジューラの状態をリセットします。 >false
により、flushSchedulerQueue
が次のティックのコールバックに渡されるようになります。は 1 ティックに 1 回だけ渡すことができます。nextTick
に渡されるコールバックflushSchedulerQueue
が何をするのか見てみましょう: -
nextTick
メソッドでflushSchedulerQueue
を実行します > メソッド、このメソッドは、queue
内のウォッチャーのrun
メソッドを 1 つずつ実行します。まず、キュー内のウォッチャーを ID によって小さいものから大きいものに並べ替えるqueue.sort()
メソッドがあることがわかります。これにより、次のことが保証されます。 - コンポーネントの更新の順序は次のとおりです。親コンポーネントは常に子コンポーネントより前に作成されるため、親コンポーネントから子コンポーネントへの順序。 🎜🎜コンポーネントのユーザー ウォッチャー (リスナー ウォッチャー) は、レンダー ウォッチャーよりも先に作成されることが多いため、レンダー ウォッチャーよりも前に実行されます🎜🎜🎜🎜 親コンポーネント ウォッチャーの実行中にコンポーネントが破棄された場合、そのウォッチャーの実行はスキップされます🎜🎜
// src/core/util/next-tick.js 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 if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } /* 强制使用macrotask的方法 */ export function withMacroTask(fn: Function): Function { return fn._withTask || (fn._withTask = function() { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) }
<p> <span>{{ name }}</span> <button>change name</button> </p><p></p> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93' } }, methods: { change() { const $name = this.$refs.name this.$nextTick(() => console.log('setter前:' + $name.innerHTML)) this.name = ' name改喽 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + $name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML)) } } }) </script>
index 実行中に既存のウォッチャー オブジェクトが処理されるため、長さはここではキャッシュされません。この期間中、より多くのウォッチャー オブジェクトがキューにプッシュされる可能性があります。 🎜🎜その後、データ変更のプロセスがモデル層からビューに反映されます。 code>🎜 <h2 id="nextTick-の原理">2. nextTick の原理</h2>
<h3 id="マクロタスク-マイクロタスク">2.1 マクロタスク/マイクロタスク</h3>🎜ここでは、各ウォッチャーの実行を含み、<code>nextTick に渡されるメソッドを見ていきます。
をコールバックとして使用 > その後、nextTick
がこのメソッドで何かを実行します。ただし、その前に、ブラウザの EventLoop
、macro task
、micro task
の概念を理解する必要があります。理解できない場合は、次のことを行ってください。 JS と Node.js のイベント ループに関するこの記事を参照してください。メイン スレッドでの後者の 2 つの実行関係を示す図があります。🎜🎜🎜🎜🎜🎜メイン スレッドが完了したときの説明同期タスク: 🎜🎜🎜🎜 エンジンは、最初にマクロタスクキューから最初のタスクを取り出し、実行が完了したら、マイクロタスクキュー内のすべてのタスクを取り出し、それらを順番に実行します。マクロタスク キューから最初のタスクを取り出します。実行が完了すると、マイクロタスク キュー内のすべてのタスクが取り出されます。🎜🎜🎜🎜 は、両方のキュー内のすべてのタスクが取り出されるまで繰り返されます。 🎜浏览器环境中常见的异步任务种类,按照优先级:
macro task
:同步代码、setImmediate
、MessageChannel
、setTimeout/setInterval
micro task
:Promise.then
、MutationObserver
有的文章把 micro task
叫微任务,macro task
叫宏任务,因为这两个单词拼写太像了 -。- ,所以后面的注释多用中文表示~
先来看看源码中对 micro task
与 macro task
的实现: macroTimerFunc
、microTimerFunc
// src/core/util/next-tick.js const callbacks = [] // 存放异步执行的回调 let pending = false // 一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送 /* 挨个同步执行callbacks中回调 */ function flushCallbacks() { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i { setImmediate(flushCallbacks) } } else if (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 (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { microTimerFunc = macroTimerFunc // fallback to macro }
flushCallbacks
这个方法就是挨个同步的去执行callbacks中的回调函数们,callbacks中的回调函数是在调用 nextTick
的时候添加进去的;那么怎么去使用 micro task
与 macro task
去执行 flushCallbacks
呢,这里他们的实现 macroTimerFunc
、microTimerFunc
使用浏览器中宏任务/微任务的API对flushCallbacks
方法进行了一层包装。比如宏任务方法 macroTimerFunc=()=>{ setImmediate(flushCallbacks) }
,这样在触发宏任务执行的时候 macroTimerFunc()
就可以在浏览器中的下一个宏任务loop的时候消费这些保存在callbacks数组中的回调了,微任务同理。同时也可以看出传给 nextTick
的异步回调函数是被压成了一个同步任务在一个tick执行完的,而不是开启多个异步任务。
注意这里有个比较难理解的地方,第一次调用 nextTick
的时候 pending
为false,此时已经push到浏览器event loop中一个宏任务或微任务的task,如果在没有flush掉的情况下继续往callbacks里面添加,那么在执行这个占位queue的时候会执行之后添加的回调,所以 macroTimerFunc
、microTimerFunc
相当于task queue的占位,以后 pending
为true则继续往占位queue里面添加,event loop轮到这个task queue的时候将一并执行。执行 flushCallbacks
时 pending
置false,允许下一轮执行 nextTick
时往event loop占位。
可以看到上面 macroTimerFunc
与 microTimerFunc
进行了在不同浏览器兼容性下的平稳退化,或者说降级策略:
macroTimerFunc
:setImmediate -> MessageChannel -> setTimeout
。首先检测是否原生支持setImmediate
,这个方法只在 IE、Edge 浏览器中原生实现,然后检测是否支持 MessageChannel,如果对MessageChannel
不了解可以参考一下这篇文章,还不支持的话最后使用setTimeout
;
为什么优先使用setImmediate
与MessageChannel
而不直接使用setTimeout
呢,是因为HTML5规定setTimeout执行的最小延时为4ms,而嵌套的timeout表现为10ms,为了尽可能快的让回调执行,没有最小延时限制的前两者显然要优于setTimeout
。microTimerFunc
:Promise.then -> macroTimerFunc
。首先检查是否支持Promise
,如果支持的话通过Promise.then
来调用flushCallbacks
方法,否则退化为macroTimerFunc
;
vue2.5之后nextTick
中因为兼容性原因删除了微任务平稳退化的MutationObserver
的方式。
2.2 nextTick实现
最后来看看我们平常用到的 nextTick
方法到底是如何实现的:
// src/core/util/next-tick.js 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 if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } /* 强制使用macrotask的方法 */ export function withMacroTask(fn: Function): Function { return fn._withTask || (fn._withTask = function() { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) }
nextTick
在这里分为三个部分,我们一起来看一下;
首先
nextTick
把传入的cb
回调函数用try-catch
包裹后放在一个匿名函数中推入callbacks数组中,这么做是因为防止单个cb
如果执行错误不至于让整个JS线程挂掉,每个cb
都包裹是防止这些回调函数如果执行错误不会相互影响,比如前一个抛错了后一个仍然可以执行。然后检查
pending
状态,这个跟之前介绍的queueWatcher
中的waiting
是一个意思,它是一个标记位,一开始是false
在进入macroTimerFunc
、microTimerFunc
方法前被置为true
,因此下次调用nextTick
就不会进入macroTimerFunc
、microTimerFunc
方法,这两个方法中会在下一个macro/micro tick
时候flushCallbacks
异步的去执行callbacks队列中收集的任务,而flushCallbacks
方法在执行一开始会把pending
置false
,因此下一次调用nextTick
时候又能开启新一轮的macroTimerFunc
、microTimerFunc
,这样就形成了vue中的event loop
。最后检查是否传入了
cb
,因为nextTick
还支持Promise化的调用:nextTick().then(() => {})
,所以如果没有传入cb
就直接return了一个Promise实例,并且把resolve传递给_resolve,这样后者执行的时候就跳到我们调用的时候传递进then
的方法中。
Vue源码中 next-tick.js
文件还有一段重要的注释,这里就翻译一下:
在vue2.5之前的版本中,nextTick基本上基于micro task
来实现的,但是在某些情况下micro task
具有太高的优先级,并且可能在连续顺序事件之间(例如#4521,#6690)或者甚至在同一事件的事件冒泡过程中之间触发(#6566)。但是如果全部都改成macro task
,对一些有重绘和动画的场景也会有性能影响,如 issue #6813。vue2.5之后版本提供的解决办法是默认使用micro task
,但在需要时(例如在v-on附加的事件处理程序中)强制使用macro task
。
为什么默认优先使用 micro task
呢,是利用其高优先级的特性,保证队列中的微任务在一次循环全部执行完毕。
强制 macro task
的方法是在绑定 DOM 事件的时候,默认会给回调的 handler 函数调用 withMacroTask
方法做一层包装 handler = withMacroTask(handler)
,它保证整个回调函数执行过程中,遇到数据状态的改变,这些改变都会被推到 macro task
中。以上实现在 src/platforms/web/runtime/modules/events.js 的 add
方法中,可以自己看一看具体代码。
刚好在写这篇文章的时候思否上有人问了个问题 vue 2.4 和2.5 版本的@input事件不一样 ,这个问题的原因也是因为2.5之前版本的DOM事件采用 micro task
,而之后采用 macro task
,解决的途径参考 中介绍的几个办法,这里就提供一个在mounted钩子中用 addEventListener
添加原生事件的方法来实现,参见 CodePen。
3. 一个例子
说这么多,不如来个例子,执行参见 CodePen
<p> <span>{{ name }}</span> <button>change name</button> </p><p></p> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93' } }, methods: { change() { const $name = this.$refs.name this.$nextTick(() => console.log('setter前:' + $name.innerHTML)) this.name = ' name改喽 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + $name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML)) } } }) </script>
执行以下看看结果:
同步方式:SHERlocked93 setter前:SHERlocked93 setter后:name改喽 Promise方式:name改喽 setTimeout方式:name改喽
为什么是这样的结果呢,解释一下:
同步方式: 当把data中的name修改之后,此时会触发name的
setter
中的dep.notify
通知依赖本data的render watcher去update
,update
会把flushSchedulerQueue
函数传递给nextTick
,render watcher在flushSchedulerQueue
函数运行时watcher.run
再走diff -> patch
那一套重渲染re-render
视图,这个过程中会重新依赖收集,这个过程是异步的;所以当我们直接修改了name之后打印,这时异步的改动还没有被patch
到视图上,所以获取视图上的DOM元素还是原来的内容。setter の前: 元のコンテンツが setter の前に出力されるのはなぜですか? それは、
nextTick
が呼び出されたときにコールバックを 1 つずつコールバック配列にプッシュするためであり、後で実行する場合にも同じことが当てはまります。 for
schedulerQueue に入力する、先入れ先出し方式のキューの概念に似ています。 code> キューとその実行関数 <code>flushSchedulerQueue
が nextTick
に渡されます。この時点で、コールバック キューにはすでに pre-setter function
が存在します。 、この cb
は setter function
の後にコールバック キューにプッシュされた場合、先入れ先出しベースでコールバック内でコールバックを実行するときに、 であるためです。 >setter 関数
が最初に実行され、この時点ではレンダー ウォッチャーは実行されないため、印刷された DOM 要素には元のコンテンツが残ります。 nextTick
在被调用的时候把回调挨个push进callbacks数组,之后执行的时候也是 for
循环出来挨个执行,所以是类似于队列这样一个概念,先入先出;在修改name之后,触发把render watcher填入 schedulerQueue
队列并把他的执行函数 flushSchedulerQueue
传递给 nextTick
,此时callbacks队列中已经有了 setter前函数
了,因为这个 cb
是在 setter前函数
之后被push进callbacks队列的,那么先入先出的执行callbacks中回调的时候先执行 setter前函数
,这时并未执行render watcher的 watcher.run
,所以打印DOM元素仍然是原来的内容。setter后: setter后这时已经执行完 flushSchedulerQueue
,这时render watcher已经把改动 patch
到视图上,所以此时获取DOM是改过之后的内容。
Promise方式: 相当于 Promise.then
的方式执行这个函数,此时DOM已经更改。
setTimeout方式: 最后执行macro task的任务,此时DOM已经更改。
注意,在执行 setter前函数
这个异步任务之前,同步的代码已经执行完毕,异步的任务都还未执行,所有的 $nextTick
函数也执行完毕,所有回调都被push进了callbacks队列中等待执行,所以在setter前函数
执行的时候,此时callbacks队列是这样的:[setter前函数
,flushSchedulerQueue
,setter后函数
,Promise方式函数
],它是一个micro task队列,执行完毕之后执行macro task setTimeout
,所以打印出上面的结果。
另外,如果浏览器的宏任务队列里面有setImmediate
、MessageChannel
、setTimeout/setInterval
各种类型的任务,那么会按照上面的顺序挨个按照添加进event loop中的顺序执行,所以如果浏览器支持MessageChannel
, nextTick
执行的是 macroTimerFunc
,那么如果 macrotask queue 中同时有 nextTick
添加的任务和用户自己添加的 setTimeout
类型的任务,会优先执行 nextTick
中的任务,因为MessageChannel
的优先级比 setTimeout
的高,setImmediate
セッターの後、flushSchedulerQueue
が実行されているこの時点で、レンダー ウォッチャーはすでに変更 patch
をビューに適用しているため、このときのDOMは変更後のContentです。
Promise メソッド:
DOM が変更されたときにこの関数を実行するPromise.then
と同等です。 setTimeout メソッド: 🎜 DOM が変更されたときに、最後にマクロ タスクを実行します。 🎜🎜🎜 pre-setter function
の非同期タスクを実行する前に、同期コードが実行されており、すべての $nextTick の非同期タスクがまだ実行されていないことに注意してください。 code> 関数も実行され、すべてのコールバックがコールバック キューにプッシュされて実行を待機しているため、<code>pre-setter 関数
が実行されると、コールバック キューは次のようになります。 [プリセッター関数 Function
, flushSchedulerQueue
, setter function
, Promise mode function
]、マイクロタスクです実行後、タスク setTimeout
が実行されるので、上記の結果を出力します。 🎜🎜また、ブラウザのマクロタスクキューにsetImmediate
、MessageChannel
、setTimeout/setInterval
などの各種タスクが存在する場合、上記のシーケンスはイベント ループに追加された順序で 1 つずつ実行されるため、ブラウザが MessageChannel
をサポートしている場合、nextTick
は macroTimerFunc を実行します。
の場合、マクロタスク キューに nextTick
によって追加されたタスクと、ユーザーによって追加されたタイプ setTimeout
のタスクの両方がある場合、nextTickMessageChannel
の優先度は setTimeout
の優先度よりも高いため、/code> が最初に実行されます。これは setImmediate
にも同じことが当てはまります。 🎜🎜関連する推奨事項: 🎜🎜🎜🎜Vue で mixin を使用する方法の分析🎜🎜🎜🎜🎜🎜🎜Vue2.0 のカスタム命令とインスタンスの属性とメソッド🎜🎜🎜以上がVue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











前書き: vue3 の開発では、reactive は応答性の高いデータを実装するメソッドを提供します。これは日常の開発で頻繁に使用される API です。この記事では、著者はその内部動作メカニズムを探ります。

vscode 自体は、定義にジャンプするための Vue ファイル コンポーネントをサポートしていますが、そのサポートは非常に弱いです。 vue-cli の構成では、多くの柔軟な使用法を記述することができるため、制作効率が向上します。しかし、これらの柔軟な記述方法により、vscode 自体が提供する関数がファイル定義へのジャンプをサポートできなくなります。これらの柔軟な記述方法に対応し、作業効率を向上させるために、Vue ファイルの定義へのジャンプをサポートする vscode プラグインを作成しました。

Ace は、JavaScript で書かれた埋め込み可能なコード エディターです。 Sublime、Vim、TextMate などのネイティブ エディターの機能とパフォーマンスに匹敵します。あらゆる Web ページや JavaScript アプリケーションに簡単に埋め込むことができます。 Ace は Cloud9 IDE のメイン エディタとして維持されており、Mozilla Skywriter (Bespin) プロジェクトの後継です。

コンポーネント化とモジュール化の違い: モジュール化はコード ロジックの観点から分割され、コードの階層化された開発が容易になり、各機能モジュールの機能の一貫性が保証されます。コンポーネント化は UI インターフェイスの観点から計画され、フロントエンドのコンポーネント化により UI コンポーネントの再利用が容易になります。

Vue.js は、今日のフロントエンド開発において非常に人気のあるフレームワークとなっています。 Vue.js が進化し続けるにつれて、単体テストの重要性がますます高まっています。今日は、Vue.js 3 で単体テストを作成する方法を検討し、いくつかのベスト プラクティスと一般的な問題と解決策を提供します。

Vue3 動的コンポーネントで例外を処理するにはどうすればよいですか?次の記事では、Vue3 の動的コンポーネントの例外処理方法について説明します。

Vue フレームワークを使用してフロントエンド プロジェクトを開発する場合、デプロイ時に複数の環境をデプロイすることになりますが、開発環境、テスト環境、オンライン環境で呼び出されるインターフェイス ドメイン名が異なることがよくあります。どうすれば区別できるでしょうか?それは環境変数とパターンを使用することです。

Vue3 のリリース以来、単語合成 API は Vue を書く学生の視野に入ってきました。合成 API が以前のオプション API よりもどれほど優れているかは誰もが聞いたことがあると思います。そして、@ のリリースにより、 vue/composition-api プラグイン、Vue2 学生もバスに乗れます. 次に、主に Response ref と Reactive を使用して、このプラグインがどのようにこれを実現しているかを詳細に分析します。
