ホームページ  >  記事  >  ウェブフロントエンド  >  Vue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析

Vue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析

不言
不言オリジナル
2018-07-20 11:53:502465ブラウズ

この記事では、Vue ソース コードのバッチ非同期更新と nextTick 原則の分析を紹介します。必要な方は参考にしてください。

vue はすでに国内のフロントエンド Web エンドの 3 分の 1 を占めており、私も日常的に使用している主要なテクノロジー スタックの 1 つであり、その理由についても興味があります。このような記事については、この機会に皆さんの記事やディスカッションからいくつかのヒントを得ると同時に、ソースコードを読んだときにいくつかの考えをまとめて、いくつかの記事を作成します。私自身の考え方の要約

ターゲット Vue バージョン: 2.5 .17-beta.02.5.17-beta.0

vue源码注释:https://github.com/SHERlocked93/vue-analysis

声明:文章中源码的语法都使用 Flow,并且源码根据需要都有删节(为了不被迷糊 @_@),如果要看完整版的请进入上面的github地址,本文是系列文章,文章地址见底部~

1. 异步更新

我们在依赖收集原理的响应式化方法 defineReactive 中的 setter 访问器中有派发更新 dep.notify() 方法,这个方法会挨个通知在 depsubs 中收集的订阅自己变动的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到队列中不传递 flushSchedulerQueuenextTick,等到 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从小到大排了个序,这样做可以保证:

  1. 组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。

  2. 一个组件的user watchers(侦听器watcher)比render watcher先运行,因为user watchers往往比render watcher更早创建

  3. 如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过

在挨个执行队列中的for循环中,index 这里没有将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue。

那么数据的修改从model层反映到view的过程:数据更改 -> setter -> Dep -> Watcher -> nextTick -> patch -> 更新视图

2. nextTick原理

2.1 宏任务/微任务

这里就来看看包含着每个watcher执行的方法被作为回调传入 nextTick 之后,nextTick 对这个方法做了什么。不过首先要了解一下浏览器中的 EventLoopmacro taskmicro task

vue ソース コード コメント: https://github.com/SHERlocked93/vue-analysis

Vue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析ステートメント:記事内のソース コードの構文は 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 を見てください:
    // 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
      })
    }
  1. ここでは has のハッシュ マップが使用されています。現在のウォッチャーの ID が存在するかどうかを確認し、すでに存在する場合はスキップし、存在しない場合は queue キューにプッシュし、ハッシュ テーブルにマークを付けます。繰り返しの追加を防ぐために次の検査を行います。これは、重複をチェックするために毎回キューにアクセスするよりも効率的なプロセスであり、この方法では、同じウォッチャーに対する patch の変更が繰り返されなくなります。 100 件の変更が同時に行われた場合でも、セカンダリ ビューで使用されるデータは、非同期 patch 中の最後の変更でのみ更新されます。

    ここでの waiting メソッドは、flushSchedulerQueuenextTick のタグ ビットに渡されたかどうかをマークするために使用されます。 flushSchedulerQueuenextTick に渡さないと、resetSchedulerStatewaiting に戻されます。 /code> はスケジューラの状態をリセットします。 >false により、flushSchedulerQueue が次のティックのコールバックに渡されるようになります。は 1 ティックに 1 回だけ渡すことができます。 nextTick に渡されるコールバック flushSchedulerQueue が何をするのか見てみましょう:
  2. <p>
      <span>{{ name }}</span>
      <button>change name</button>
      </p><p></p>
    
    <script>
      new Vue({
        el: &#39;#app&#39;,
        data() {
          return {
            name: &#39;SHERlocked93&#39;
          }
        },
        methods: {
          change() {
            const $name = this.$refs.name
            this.$nextTick(() => console.log(&#39;setter前:&#39; + $name.innerHTML))
            this.name = &#39; name改喽 &#39;
            console.log(&#39;同步方式:&#39; + this.$refs.name.innerHTML)
            setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML))
            this.$nextTick(() => console.log(&#39;setter后:&#39; + $name.innerHTML))
            this.$nextTick().then(() => console.log(&#39;Promise方式:&#39; + $name.innerHTML))
          }
        }
      })
    </script>
  3. nextTick メソッドで flushSchedulerQueue を実行します > メソッド、このメソッドは、queue 内のウォッチャーの run メソッドを 1 つずつ実行します。まず、キュー内のウォッチャーを ID によって小さいものから大きいものに並べ替える queue.sort() メソッドがあることがわかります。これにより、次のことが保証されます。

  4. コンポーネントの更新の順序は次のとおりです。親コンポーネントは常に子コンポーネントより前に作成されるため、親コンポーネントから子コンポーネントへの順序。

    🎜🎜コンポーネントのユーザー ウォッチャー (リスナー ウォッチャー) は、レンダー ウォッチャーよりも先に作成されることが多いため、レンダー ウォッチャーよりも前に実行されます🎜🎜🎜🎜 親コンポーネント ウォッチャーの実行中にコンポーネントが破棄された場合、そのウォッチャーの実行はスキップされます🎜🎜
🎜 実行キューの for ループで 1 つずつ index 実行中に既存のウォッチャー オブジェクトが処理されるため、長さはここではキャッシュされません。この期間中、より多くのウォッチャー オブジェクトがキューにプッシュされる可能性があります。 🎜🎜その後、データ変更のプロセスがモデル層からビューに反映されます。 code>🎜 <h2>2. nextTick の原理</h2> <h3>2.1 マクロタスク/マイクロタスク</h3>🎜ここでは、各ウォッチャーの実行を含み、<code>nextTick に渡されるメソッドを見ていきます。 をコールバックとして使用 > その後、nextTick がこのメソッドで何かを実行します。ただし、その前に、ブラウザの EventLoopmacro taskmicro task の概念を理解する必要があります。理解できない場合は、次のことを行ってください。 JS と Node.js のイベント ループに関するこの記事を参照してください。メイン スレッドでの後者の 2 つの実行関係を示す図があります。🎜🎜🎜🎜🎜🎜メイン スレッドが完了したときの説明同期タスク: 🎜🎜🎜🎜 エンジンは、最初にマクロタスクキューから最初のタスクを取り出し、実行が完了したら、マイクロタスクキュー内のすべてのタスクを取り出し、それらを順番に実行します。マクロタスク キューから最初のタスクを取り出します。実行が完了すると、マイクロタスク キュー内のすべてのタスクが取り出されます。🎜🎜🎜🎜 は、両方のキュー内のすべてのタスクが取り出されるまで繰り返されます。 🎜

浏览器环境中常见的异步任务种类,按照优先级:

  • macro task :同步代码、setImmediateMessageChannelsetTimeout/setInterval

  • micro taskPromise.thenMutationObserver

有的文章把 micro task 叫微任务,macro task 叫宏任务,因为这两个单词拼写太像了 -。- ,所以后面的注释多用中文表示~

先来看看源码中对 micro task macro task 的实现: macroTimerFuncmicroTimerFunc

// 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 taskmacro task 去执行 flushCallbacks 呢,这里他们的实现 macroTimerFuncmicroTimerFunc 使用浏览器中宏任务/微任务的API对flushCallbacks 方法进行了一层包装。比如宏任务方法 macroTimerFunc=()=>{ setImmediate(flushCallbacks) },这样在触发宏任务执行的时候 macroTimerFunc() 就可以在浏览器中的下一个宏任务loop的时候消费这些保存在callbacks数组中的回调了,微任务同理。同时也可以看出传给 nextTick 的异步回调函数是被压成了一个同步任务在一个tick执行完的,而不是开启多个异步任务。

注意这里有个比较难理解的地方,第一次调用 nextTick 的时候 pending 为false,此时已经push到浏览器event loop中一个宏任务或微任务的task,如果在没有flush掉的情况下继续往callbacks里面添加,那么在执行这个占位queue的时候会执行之后添加的回调,所以 macroTimerFuncmicroTimerFunc 相当于task queue的占位,以后 pending 为true则继续往占位queue里面添加,event loop轮到这个task queue的时候将一并执行。执行 flushCallbackspending 置false,允许下一轮执行 nextTick 时往event loop占位。

可以看到上面 macroTimerFuncmicroTimerFunc 进行了在不同浏览器兼容性下的平稳退化,或者说降级策略

  1. macroTimerFuncsetImmediate -> MessageChannel -> setTimeout 。首先检测是否原生支持 setImmediate ,这个方法只在 IE、Edge 浏览器中原生实现,然后检测是否支持 MessageChannel,如果对 MessageChannel 不了解可以参考一下这篇文章,还不支持的话最后使用 setTimeout
    为什么优先使用 setImmediate MessageChannel 而不直接使用 setTimeout 呢,是因为HTML5规定setTimeout执行的最小延时为4ms,而嵌套的timeout表现为10ms,为了尽可能快的让回调执行,没有最小延时限制的前两者显然要优于 setTimeout

  2. microTimerFuncPromise.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 在这里分为三个部分,我们一起来看一下;

  1. 首先 nextTick 把传入的 cb 回调函数用 try-catch 包裹后放在一个匿名函数中推入callbacks数组中,这么做是因为防止单个 cb 如果执行错误不至于让整个JS线程挂掉,每个 cb 都包裹是防止这些回调函数如果执行错误不会相互影响,比如前一个抛错了后一个仍然可以执行。

  2. 然后检查 pending 状态,这个跟之前介绍的 queueWatcher 中的 waiting 是一个意思,它是一个标记位,一开始是 false 在进入 macroTimerFuncmicroTimerFunc 方法前被置为 true,因此下次调用 nextTick 就不会进入 macroTimerFuncmicroTimerFunc 方法,这两个方法中会在下一个 macro/micro tick 时候 flushCallbacks 异步的去执行callbacks队列中收集的任务,而 flushCallbacks 方法在执行一开始会把 pendingfalse,因此下一次调用 nextTick 时候又能开启新一轮的 macroTimerFuncmicroTimerFunc,这样就形成了vue中的 event loop

  3. 最后检查是否传入了 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: &#39;#app&#39;,
    data() {
      return {
        name: &#39;SHERlocked93&#39;
      }
    },
    methods: {
      change() {
        const $name = this.$refs.name
        this.$nextTick(() => console.log(&#39;setter前:&#39; + $name.innerHTML))
        this.name = &#39; name改喽 &#39;
        console.log(&#39;同步方式:&#39; + this.$refs.name.innerHTML)
        setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML))
        this.$nextTick(() => console.log(&#39;setter后:&#39; + $name.innerHTML))
        this.$nextTick().then(() => console.log(&#39;Promise方式:&#39; + $name.innerHTML))
      }
    }
  })
</script>

执行以下看看结果:

同步方式:SHERlocked93 
setter前:SHERlocked93 
setter后:name改喽 
Promise方式:name改喽 
setTimeout方式:name改喽

为什么是这样的结果呢,解释一下:

  1. 同步方式: 当把data中的name修改之后,此时会触发name的 setter 中的 dep.notify 通知依赖本data的render watcher去 updateupdate 会把 flushSchedulerQueue 函数传递给 nextTick,render watcher在 flushSchedulerQueue 函数运行时 watcher.run 再走 diff -> patch 那一套重渲染 re-render 视图,这个过程中会重新依赖收集,这个过程是异步的;所以当我们直接修改了name之后打印,这时异步的改动还没有被 patch 到视图上,所以获取视图上的DOM元素还是原来的内容。

  2. setter の前: 元のコンテンツが setter の前に出力されるのはなぜですか? それは、nextTick が呼び出されたときにコールバックを 1 つずつコールバック配列にプッシュするためであり、後で実行する場合にも同じことが当てはまります。 for

はループアウトして 1 つずつ実行するため、名前を変更した後、レンダー ウォッチャーをトリガーして schedulerQueue に入力する、先入れ先出し方式のキューの概念に似ています。 code> キューとその実行関数 <code>flushSchedulerQueuenextTick に渡されます。この時点で、コールバック キューにはすでに pre-setter function が存在します。 、この cbsetter 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前函数flushSchedulerQueuesetter后函数Promise方式函数],它是一个micro task队列,执行完毕之后执行macro task setTimeout,所以打印出上面的结果。

    另外,如果浏览器的宏任务队列里面有setImmediateMessageChannelsetTimeout/setInterval 各种类型的任务,那么会按照上面的顺序挨个按照添加进event loop中的顺序执行,所以如果浏览器支持MessageChannelnextTick 执行的是 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 が実行されるので、上記の結果を出力します。 🎜🎜また、ブラウザのマクロタスクキューにsetImmediateMessageChannelsetTimeout/setIntervalなどの各種タスクが存在する場合、上記のシーケンスはイベント ループに追加された順序で 1 つずつ実行されるため、ブラウザが MessageChannel をサポートしている場合、nextTickmacroTimerFunc を実行します。 の場合、マクロタスク キューに nextTick によって追加されたタスクと、ユーザーによって追加されたタイプ setTimeout のタスクの両方がある場合、nextTickMessageChannel の優先度は setTimeout の優先度よりも高いため、/code> が最初に実行されます。これは setImmediate にも同じことが当てはまります。 🎜🎜関連する推奨事項: 🎜🎜🎜🎜Vue で mixin を使用する方法の分析🎜🎜🎜🎜🎜🎜🎜Vue2.0 のカスタム命令とインスタンスの属性とメソッド🎜🎜🎜

    以上がVue ソース コードにおけるバッチ非同期更新と nextTick 原理の分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。