Parcourir la source

perf(runtime-core): avoid duplicate postFlushCb invocation

Also improve flush performance by using for loop instead of shift()

fix #1595
Evan You il y a 6 ans
Parent
commit
165068dbc2

+ 16 - 0
packages/runtime-core/__tests__/scheduler.spec.ts

@@ -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'])
+  })
 })

+ 33 - 14
packages/runtime-core/src/scheduler.ts

@@ -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!