Skip to content

Commit c8533b8

Browse files
author
Evan You
committed
batcher rewrite - batch vm.$watch callbacks
1 parent 1620621 commit c8533b8

File tree

6 files changed

+147
-49
lines changed

6 files changed

+147
-49
lines changed

src/batcher.js

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
1-
var utils = require('./utils'),
2-
queue, has, waiting
1+
var utils = require('./utils')
32

4-
reset()
3+
function Batcher () {
4+
this.reset()
5+
}
6+
7+
var BatcherProto = Batcher.prototype
58

6-
exports.queue = function (binding) {
7-
if (!has[binding.id]) {
8-
queue.push(binding)
9-
has[binding.id] = true
10-
if (!waiting) {
11-
waiting = true
12-
utils.nextTick(flush)
9+
BatcherProto.push = function (job) {
10+
if (!this.has[job.id]) {
11+
this.queue.push(job)
12+
this.has[job.id] = job
13+
if (!this.waiting) {
14+
this.waiting = true
15+
utils.nextTick(utils.bind(this.flush, this))
1316
}
17+
} else if (job.override) {
18+
var oldJob = this.has[job.id]
19+
oldJob.cancelled = true
20+
this.queue.push(job)
21+
this.has[job.id] = job
1422
}
1523
}
1624

17-
function flush () {
18-
for (var i = 0; i < queue.length; i++) {
19-
var b = queue[i]
20-
if (b.unbound) continue
21-
b._update()
22-
has[b.id] = false
25+
BatcherProto.flush = function () {
26+
// before flush hook
27+
if (this._preFlush) this._preFlush()
28+
// do not cache length because more jobs might be pushed
29+
// as we execute existing jobs
30+
for (var i = 0; i < this.queue.length; i++) {
31+
var job = this.queue[i]
32+
if (job.cancelled) continue
33+
if (job.execute() !== false) {
34+
this.has[job.id] = false
35+
}
2336
}
24-
reset()
37+
this.reset()
38+
}
39+
40+
BatcherProto.reset = function () {
41+
this.has = utils.hash()
42+
this.queue = []
43+
this.waiting = false
2544
}
2645

27-
function reset () {
28-
queue = []
29-
has = utils.hash()
30-
waiting = false
31-
}
46+
module.exports = Batcher

src/binding.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
var batcher = require('./batcher'),
2-
id = 0
1+
var Batcher = require('./batcher'),
2+
bindingBatcher = new Batcher(),
3+
bindingId = 0
34

45
/**
56
* Binding class.
@@ -9,7 +10,7 @@ var batcher = require('./batcher'),
910
* and multiple computed property dependents
1011
*/
1112
function Binding (compiler, key, isExp, isFn) {
12-
this.id = id++
13+
this.id = bindingId++
1314
this.value = undefined
1415
this.isExp = !!isExp
1516
this.isFn = isFn
@@ -32,7 +33,17 @@ BindingProto.update = function (value) {
3233
this.value = value
3334
}
3435
if (this.dirs.length || this.subs.length) {
35-
batcher.queue(this)
36+
var self = this
37+
bindingBatcher.push({
38+
id: this.id,
39+
execute: function () {
40+
if (!self.unbound) {
41+
self._update()
42+
} else {
43+
return false
44+
}
45+
}
46+
})
3647
}
3748
}
3849

src/compiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ CompilerProto.setupObserver = function () {
197197
// a hash to hold event proxies for each root level key
198198
// so they can be referenced and removed later
199199
observer.proxies = makeHash()
200+
observer._ctx = compiler.vm
200201

201202
// add own listeners which trigger binding updates
202203
observer

src/viewmodel.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
var Compiler = require('./compiler'),
22
utils = require('./utils'),
33
transition = require('./transition'),
4+
Batcher = require('./batcher'),
5+
slice = [].slice,
46
def = utils.defProtected,
5-
nextTick = utils.nextTick
7+
nextTick = utils.nextTick,
8+
9+
// batch $watch callbacks
10+
watcherBatcher = new Batcher(),
11+
watcherId = 0
612

713
/**
814
* ViewModel exposed to the user that holds data,
@@ -36,11 +42,17 @@ def(VMProto, '$set', function (key, value) {
3642
* fire callback with new value
3743
*/
3844
def(VMProto, '$watch', function (key, callback) {
39-
var self = this
45+
// save a unique id for each watcher
46+
var id = watcherId++,
47+
self = this
4048
function on () {
41-
var args = arguments
42-
utils.nextTick(function () {
43-
callback.apply(self, args)
49+
var args = slice.call(arguments)
50+
watcherBatcher.push({
51+
id: id,
52+
override: true,
53+
execute: function () {
54+
callback.apply(self, args)
55+
}
4456
})
4557
}
4658
callback._fn = on

test/unit/specs/batcher.js

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
describe('Batcher', function () {
22

3-
var batcher = require('vue/src/batcher'),
3+
var Batcher = require('vue/src/batcher'),
4+
batcher = new Batcher(),
45
nextTick = require('vue/src/utils').nextTick
56

67
var updateCount = 0
7-
function mockBinding (id, middleware) {
8+
function mockJob (id, middleware) {
89
return {
910
id: id,
10-
_update: function () {
11+
execute: function () {
1112
updateCount++
1213
this.updated = true
1314
if (middleware) middleware()
1415
}
1516
}
1617
}
1718

18-
it('should queue bindings to be updated on nextTick', function (done) {
19+
it('should push bindings to be updated on nextTick', function (done) {
1920

2021
updateCount = 0
21-
var b1 = mockBinding(1),
22-
b2 = mockBinding(2)
23-
batcher.queue(b1)
24-
batcher.queue(b2)
22+
var b1 = mockJob(1),
23+
b2 = mockJob(2)
24+
batcher.push(b1)
25+
batcher.push(b2)
2526
assert.strictEqual(updateCount, 0)
2627
assert.notOk(b1.updated)
2728
assert.notOk(b2.updated)
@@ -35,13 +36,13 @@ describe('Batcher', function () {
3536

3637
})
3738

38-
it('should not queue dupicate bindings', function (done) {
39+
it('should not push dupicate bindings', function (done) {
3940

4041
updateCount = 0
41-
var b1 = mockBinding(1),
42-
b2 = mockBinding(1)
43-
batcher.queue(b1)
44-
batcher.queue(b2)
42+
var b1 = mockJob(1),
43+
b2 = mockJob(1)
44+
batcher.push(b1)
45+
batcher.push(b2)
4546

4647
nextTick(function () {
4748
assert.strictEqual(updateCount, 1)
@@ -52,14 +53,14 @@ describe('Batcher', function () {
5253

5354
})
5455

55-
it('should queue dependency bidnings triggered during flush', function (done) {
56+
it('should push dependency bidnings triggered during flush', function (done) {
5657

5758
updateCount = 0
58-
var b1 = mockBinding(1),
59-
b2 = mockBinding(2, function () {
60-
batcher.queue(b1)
59+
var b1 = mockJob(1),
60+
b2 = mockJob(2, function () {
61+
batcher.push(b1)
6162
})
62-
batcher.queue(b2)
63+
batcher.push(b2)
6364

6465
nextTick(function () {
6566
assert.strictEqual(updateCount, 2)
@@ -70,4 +71,39 @@ describe('Batcher', function () {
7071

7172
})
7273

74+
it('should allow overriding jobs with same ID', function (done) {
75+
76+
updateCount = 0
77+
var b1 = mockJob(1),
78+
b2 = mockJob(1)
79+
80+
b2.override = true
81+
batcher.push(b1)
82+
batcher.push(b2)
83+
84+
nextTick(function () {
85+
assert.strictEqual(updateCount, 1)
86+
assert.ok(b1.cancelled)
87+
assert.notOk(b1.updated)
88+
assert.ok(b2.updated)
89+
done()
90+
})
91+
92+
})
93+
94+
it('should execute the _preFlush hook', function (done) {
95+
96+
var executed = false
97+
batcher._preFlush = function () {
98+
executed = true
99+
}
100+
batcher.push(mockJob(1))
101+
102+
nextTick(function () {
103+
assert.ok(executed)
104+
done()
105+
})
106+
107+
})
108+
73109
})

test/unit/specs/viewmodel.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,29 @@ describe('UNIT: ViewModel', function () {
8484
})
8585
})
8686

87+
it('should batch mutiple changes in a single event loop', function (done) {
88+
var callbackCount = 0,
89+
gotVal,
90+
finalValue = { b: { c: 3} },
91+
vm = new Vue({
92+
data: {
93+
a: { b: { c: 0 }}
94+
}
95+
})
96+
vm.$watch('a', function (newVal) {
97+
callbackCount++
98+
gotVal = newVal
99+
})
100+
vm.a.b.c = 1
101+
vm.a.b = { c: 2 }
102+
vm.a = finalValue
103+
nextTick(function () {
104+
assert.strictEqual(callbackCount, 1)
105+
assert.strictEqual(gotVal, finalValue)
106+
done()
107+
})
108+
})
109+
87110
})
88111

89112
describe('.$unwatch()', function () {

0 commit comments

Comments
 (0)