]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf(runtime-core): avoid duplicate postFlushCb invocation
authorEvan You <yyx990803@gmail.com>
Thu, 16 Jul 2020 02:36:41 +0000 (22:36 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 16 Jul 2020 02:36:41 +0000 (22:36 -0400)
Also improve flush performance by using for loop instead of shift()

fix #1595

packages/runtime-core/__tests__/scheduler.spec.ts
packages/runtime-core/src/scheduler.ts

index 9d8f4193507b7d34250f5d0cbfddf73138d52a78..9e0e104329a96cfbb04acf26e2dc96462908c0b8 100644 (file)
@@ -278,4 +278,20 @@ describe('scheduler', () => {
     await nextTick()
     expect(calls).toEqual(['job3', 'job2', 'job1'])
   })
+
+  // #1595
+  test('avoid duplicate postFlushCb invocation', async () => {
+    const calls: string[] = []
+    const cb1 = () => {
+      calls.push('cb1')
+      queuePostFlushCb(cb2)
+    }
+    const cb2 = () => {
+      calls.push('cb2')
+    }
+    queuePostFlushCb(cb1)
+    queuePostFlushCb(cb2)
+    await nextTick()
+    expect(calls).toEqual(['cb1', 'cb2'])
+  })
 })
index e518427d27095d1253351e5ec881818ee244db37..8a775b39341d2a9d151c41162e01bd07df924b10 100644 (file)
@@ -12,6 +12,9 @@ const p = Promise.resolve()
 
 let isFlushing = false
 let isFlushPending = false
+let flushIndex = 0
+let pendingPostFlushCbs: Function[] | null = null
+let pendingPostFlushIndex = 0
 
 const RECURSION_LIMIT = 100
 type CountMap = Map<Job | Function, number>
@@ -21,7 +24,7 @@ export function nextTick(fn?: () => void): Promise<void> {
 }
 
 export function queueJob(job: Job) {
-  if (!queue.includes(job)) {
+  if (!queue.includes(job, flushIndex)) {
     queue.push(job)
     queueFlush()
   }
@@ -36,8 +39,16 @@ export function invalidateJob(job: Job) {
 
 export function queuePostFlushCb(cb: Function | Function[]) {
   if (!isArray(cb)) {
-    postFlushCbs.push(cb)
+    if (
+      !pendingPostFlushCbs ||
+      !pendingPostFlushCbs.includes(cb, pendingPostFlushIndex)
+    ) {
+      postFlushCbs.push(cb)
+    }
   } else {
+    // if cb is an array, it is a component lifecycle hook which can only be
+    // triggered by a job, which is already deduped in the main queue, so
+    // we can skip dupicate check here to improve perf
     postFlushCbs.push(...cb)
   }
   queueFlush()
@@ -52,17 +63,23 @@ function queueFlush() {
 
 export function flushPostFlushCbs(seen?: CountMap) {
   if (postFlushCbs.length) {
-    const cbs = [...new Set(postFlushCbs)]
+    pendingPostFlushCbs = [...new Set(postFlushCbs)]
     postFlushCbs.length = 0
     if (__DEV__) {
       seen = seen || new Map()
     }
-    for (let i = 0; i < cbs.length; i++) {
+    for (
+      pendingPostFlushIndex = 0;
+      pendingPostFlushIndex < pendingPostFlushCbs.length;
+      pendingPostFlushIndex++
+    ) {
       if (__DEV__) {
-        checkRecursiveUpdates(seen!, cbs[i])
+        checkRecursiveUpdates(seen!, pendingPostFlushCbs[pendingPostFlushIndex])
       }
-      cbs[i]()
+      pendingPostFlushCbs[pendingPostFlushIndex]()
     }
+    pendingPostFlushCbs = null
+    pendingPostFlushIndex = 0
   }
 }
 
@@ -71,7 +88,6 @@ const getId = (job: Job) => (job.id == null ? Infinity : job.id)
 function flushJobs(seen?: CountMap) {
   isFlushPending = false
   isFlushing = true
-  let job
   if (__DEV__) {
     seen = seen || new Map()
   }
@@ -87,15 +103,18 @@ function flushJobs(seen?: CountMap) {
   // during execution of another flushed job.
   queue.sort((a, b) => getId(a!) - getId(b!))
 
-  while ((job = queue.shift()) !== undefined) {
-    if (job === null) {
-      continue
-    }
-    if (__DEV__) {
-      checkRecursiveUpdates(seen!, job)
+  for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
+    const job = queue[flushIndex]
+    if (job) {
+      if (__DEV__) {
+        checkRecursiveUpdates(seen!, job)
+      }
+      callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
     }
-    callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
   }
+  flushIndex = 0
+  queue.length = 0
+
   flushPostFlushCbs(seen)
   isFlushing = false
   // some postFlushCb queued jobs!