Skip to content

Commit 2c6930a

Browse files
committed
further improve scheduler
1 parent 2456cd2 commit 2c6930a

File tree

2 files changed

+67
-83
lines changed

2 files changed

+67
-83
lines changed

src/core/observer/scheduler.js

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ import {
88
devtools
99
} from '../util/index'
1010

11-
// We have two separate queues: one for internal component re-render updates
12-
// and one for user watcher registered via $watch(). We want to guarantee
13-
// re-render updates to be called before user watchers so that when user
14-
// watchers are triggered, the DOM would already be in updated state.
15-
1611
const queue: Array<Watcher> = []
17-
const userQueue: Array<Watcher> = []
1812
let has: { [key: number]: ?true } = {}
1913
let circular: { [key: number]: number } = {}
2014
let waiting = false
@@ -26,7 +20,6 @@ let index = 0
2620
*/
2721
function resetSchedulerState () {
2822
queue.length = 0
29-
userQueue.length = 0
3023
has = {}
3124
if (process.env.NODE_ENV !== 'production') {
3225
circular = {}
@@ -39,31 +32,17 @@ function resetSchedulerState () {
3932
*/
4033
function flushSchedulerQueue () {
4134
flushing = true
42-
runSchedulerQueue(userQueue)
43-
runSchedulerQueue(queue.sort(queueSorter))
44-
// devtool hook
45-
/* istanbul ignore if */
46-
if (devtools && config.devtools) {
47-
devtools.emit('flush')
48-
}
49-
resetSchedulerState()
50-
}
5135

52-
/**
53-
* Sort queue before flush.
54-
* This ensures components are updated from parent to child
55-
* so there will be no duplicate updates, e.g. a child was
56-
* pushed into the queue first and then its parent's props
57-
* changed.
58-
*/
59-
function queueSorter (a: Watcher, b: Watcher) {
60-
return a.id - b.id
61-
}
36+
// Sort queue before flush.
37+
// This ensures that:
38+
// 1. Components are updated from parent to child. (because parent is always
39+
// created before the child)
40+
// 2. A component's user watchers are run before its render watcher (because
41+
// user watchers are created before the render watcher)
42+
// 3. If a component is destroyed during a parent component's watcher run,
43+
// its watchers can be skipped.
44+
queue.sort((a, b) => a.id - b.id)
6245

63-
/**
64-
* Run the watchers in a single queue.
65-
*/
66-
function runSchedulerQueue (queue: Array<Watcher>) {
6746
// do not cache length because more watchers might be pushed
6847
// as we run existing watchers
6948
for (index = 0; index < queue.length; index++) {
@@ -87,7 +66,14 @@ function runSchedulerQueue (queue: Array<Watcher>) {
8766
}
8867
}
8968
}
90-
queue.length = 0
69+
70+
// devtool hook
71+
/* istanbul ignore if */
72+
if (devtools && config.devtools) {
73+
devtools.emit('flush')
74+
}
75+
76+
resetSchedulerState()
9177
}
9278

9379
/**
@@ -98,24 +84,17 @@ function runSchedulerQueue (queue: Array<Watcher>) {
9884
export function queueWatcher (watcher: Watcher) {
9985
const id = watcher.id
10086
if (has[id] == null) {
101-
// if already flushing, and all user watchers have already been run,
102-
// run the new user watcher immediately.
103-
if (flushing && watcher.user && !userQueue.length) {
104-
return watcher.run()
105-
}
106-
// push watcher into appropriate queue
10787
has[id] = true
108-
const q = watcher.user
109-
? userQueue
110-
: queue
11188
if (!flushing) {
112-
q.push(watcher)
89+
queue.push(watcher)
11390
} else {
114-
let i = q.length - 1
115-
while (i >= 0 && q[i].id > watcher.id) {
91+
// if already flushing, splice the watcher based on its id
92+
// if already past its id, it will be run next immediately.
93+
let i = queue.length - 1
94+
while (i >= 0 && queue[i].id > watcher.id) {
11695
i--
11796
}
118-
q.splice(Math.max(i, index) + 1, 0, watcher)
97+
queue.splice(Math.max(i, index) + 1, 0, watcher)
11998
}
12099
// queue the flush
121100
if (!waiting) {

test/unit/modules/observer/scheduler.spec.js

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Vue from 'vue'
12
import config from 'core/config'
23
import { queueWatcher } from 'core/observer/scheduler'
34

@@ -46,55 +47,59 @@ describe('Scheduler', () => {
4647
})
4748

4849
it('call user watchers before component re-render', done => {
49-
const vals = []
50-
function run () {
51-
vals.push(this.id)
52-
}
53-
queueWatcher({
54-
id: 2,
55-
user: true,
56-
run () {
57-
run.call(this)
58-
// user watcher triggering another directive update!
59-
queueWatcher({
60-
id: 3,
61-
run: run
62-
})
50+
const calls = []
51+
const vm = new Vue({
52+
data: {
53+
a: 1
54+
},
55+
template: '<div>{{ a }}</div>',
56+
watch: {
57+
a () { calls.push(1) }
58+
},
59+
beforeUpdate () {
60+
calls.push(2)
6361
}
64-
})
65-
queueWatcher({
66-
id: 1,
67-
run: run
68-
})
62+
}).$mount()
63+
vm.a = 2
6964
waitForUpdate(() => {
70-
expect(vals).toEqual([2, 1, 3])
65+
expect(calls).toEqual([1, 2])
7166
}).then(done)
7267
})
7368

7469
it('call user watcher triggered by component re-render immediately', done => {
7570
// this happens when a component re-render updates the props of a child
76-
const vals = []
77-
queueWatcher({
78-
id: 1,
79-
run () {
80-
vals.push(1)
81-
queueWatcher({
82-
id: 3,
83-
user: true,
84-
run () {
85-
vals.push(3)
71+
const calls = []
72+
const vm = new Vue({
73+
data: {
74+
a: 1
75+
},
76+
watch: {
77+
a () {
78+
calls.push(1)
79+
}
80+
},
81+
beforeUpdate () {
82+
calls.push(2)
83+
},
84+
template: '<div><test :a="a"></test></div>',
85+
components: {
86+
test: {
87+
props: ['a'],
88+
template: '<div>{{ a }}</div>',
89+
watch: {
90+
a () {
91+
calls.push(3)
92+
}
93+
},
94+
beforeUpdate () {
95+
calls.push(4)
8696
}
87-
})
97+
}
8898
}
89-
})
90-
queueWatcher({
91-
id: 2,
92-
run () {
93-
vals.push(2)
94-
}
95-
})
99+
}).$mount()
100+
vm.a = 2
96101
waitForUpdate(() => {
97-
expect(vals).toEqual([1, 3, 2])
102+
expect(calls).toEqual([1, 2, 3, 4])
98103
}).then(done)
99104
})
100105

0 commit comments

Comments
 (0)