Skip to content

Commit 12b4ae2

Browse files
committed
lazy evaluation for computed properties
1 parent df381a8 commit 12b4ae2

File tree

6 files changed

+106
-28
lines changed

6 files changed

+106
-28
lines changed

src/instance/scope.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var _ = require('../util')
22
var compiler = require('../compiler')
33
var Observer = require('../observer')
44
var Dep = require('../observer/dep')
5+
var Watcher = require('../watcher')
56

67
/**
78
* Setup the scope of an instance, which contains:
@@ -191,11 +192,11 @@ exports._initComputed = function () {
191192
configurable: true
192193
}
193194
if (typeof userDef === 'function') {
194-
def.get = _.bind(userDef, this)
195+
def.get = makeComputedGetter(userDef, this)
195196
def.set = noop
196197
} else {
197198
def.get = userDef.get
198-
? _.bind(userDef.get, this)
199+
? makeComputedGetter(userDef.get, this)
199200
: noop
200201
def.set = userDef.set
201202
? _.bind(userDef.set, this)
@@ -206,6 +207,21 @@ exports._initComputed = function () {
206207
}
207208
}
208209

210+
function makeComputedGetter (getter, owner) {
211+
var watcher = new Watcher(owner, getter, null, {
212+
lazy: true
213+
})
214+
return function computedGetter () {
215+
if (watcher.dirty) {
216+
watcher.evaluate()
217+
}
218+
if (Dep.target) {
219+
watcher.depend()
220+
}
221+
return watcher.value
222+
}
223+
}
224+
209225
/**
210226
* Setup instance methods. Methods must be bound to the
211227
* instance since they might be called by children

src/observer/index.js

-10
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,6 @@ Observer.create = function (value, vm) {
6464
return ob
6565
}
6666

67-
/**
68-
* Set the target watcher that is currently being evaluated.
69-
*
70-
* @param {Watcher} watcher
71-
*/
72-
73-
Observer.setTarget = function (watcher) {
74-
Dep.target = watcher
75-
}
76-
7767
// Instance methods
7868

7969
var p = Observer.prototype

src/watcher.js

+40-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
var _ = require('./util')
22
var config = require('./config')
3-
var Observer = require('./observer')
3+
var Dep = require('./observer/dep')
44
var expParser = require('./parsers/expression')
55
var batcher = require('./batcher')
66
var uid = 0
@@ -18,6 +18,7 @@ var uid = 0
1818
* - {Boolean} twoWay
1919
* - {Boolean} deep
2020
* - {Boolean} user
21+
* - {Boolean} lazy
2122
* - {Function} [preProcess]
2223
* @constructor
2324
*/
@@ -34,10 +35,12 @@ function Watcher (vm, expOrFn, cb, options) {
3435
this.deep = !!options.deep
3536
this.user = !!options.user
3637
this.twoWay = !!options.twoWay
38+
this.lazy = !!options.lazy
39+
this.dirty = this.lazy
3740
this.filters = options.filters
3841
this.preProcess = options.preProcess
3942
this.deps = []
40-
this.newDeps = []
43+
this.newDeps = null
4144
// parse expression for getter/setter
4245
if (isFn) {
4346
this.getter = expOrFn
@@ -47,7 +50,9 @@ function Watcher (vm, expOrFn, cb, options) {
4750
this.getter = res.get
4851
this.setter = res.set
4952
}
50-
this.value = this.get()
53+
this.value = this.lazy
54+
? undefined
55+
: this.get()
5156
// state for avoiding false triggers for deep and Array
5257
// watchers during vm._digest()
5358
this.queued = this.shallow = false
@@ -147,15 +152,16 @@ p.set = function (value) {
147152
*/
148153

149154
p.beforeGet = function () {
150-
Observer.setTarget(this)
155+
Dep.target = this
156+
this.newDeps = []
151157
}
152158

153159
/**
154160
* Clean up for dependency collection.
155161
*/
156162

157163
p.afterGet = function () {
158-
Observer.setTarget(null)
164+
Dep.target = null
159165
var i = this.deps.length
160166
while (i--) {
161167
var dep = this.deps[i]
@@ -164,7 +170,7 @@ p.afterGet = function () {
164170
}
165171
}
166172
this.deps = this.newDeps
167-
this.newDeps = []
173+
this.newDeps = null
168174
}
169175

170176
/**
@@ -175,7 +181,9 @@ p.afterGet = function () {
175181
*/
176182

177183
p.update = function (shallow) {
178-
if (!config.async) {
184+
if (this.lazy) {
185+
this.dirty = true
186+
} else if (!config.async) {
179187
this.run()
180188
} else {
181189
// if queued, only overwrite shallow with non-shallow,
@@ -214,6 +222,31 @@ p.run = function () {
214222
}
215223
}
216224

225+
/**
226+
* Evaluate the value of the watcher.
227+
* This only gets called for lazy watchers.
228+
*/
229+
230+
p.evaluate = function () {
231+
// avoid overwriting another watcher that is being
232+
// collected.
233+
var current = Dep.target
234+
this.value = this.get()
235+
this.dirty = false
236+
Dep.target = current
237+
}
238+
239+
/**
240+
* Depend on all deps collected by this watcher.
241+
*/
242+
243+
p.depend = function () {
244+
var i = this.deps.length
245+
while (i--) {
246+
this.deps[i].depend()
247+
}
248+
}
249+
217250
/**
218251
* Remove self from all dependencies' subcriber list.
219252
*/

test/unit/specs/instance/scope_spec.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -162,46 +162,56 @@ describe('Instance Scope', function () {
162162
var Test = Vue.extend({
163163
computed: {
164164
c: function () {
165-
expect(this).toBe(vm)
166165
return this.a + this.b
167166
},
168167
d: {
169168
get: function () {
170-
expect(this).toBe(vm)
171169
return this.a + this.b
172170
},
173171
set: function (newVal) {
174-
expect(this).toBe(vm)
175172
var vals = newVal.split(' ')
176173
this.a = vals[0]
177174
this.b = vals[1]
178175
}
176+
},
177+
// chained computed
178+
e: function () {
179+
return this.c + 'e'
179180
}
180181
}
181182
})
182183

184+
var spy = jasmine.createSpy()
183185
var vm = new Test({
184186
data: {
185187
a: 'a',
186188
b: 'b'
187189
}
188190
})
189191

192+
vm.$watch('e', spy)
193+
190194
it('get', function () {
191195
expect(vm.c).toBe('ab')
192196
expect(vm.d).toBe('ab')
197+
expect(vm.e).toBe('abe')
193198
})
194199

195-
it('set', function () {
200+
it('set', function (done) {
196201
vm.c = 123 // should do nothing
197202
vm.d = 'c d'
198203
expect(vm.a).toBe('c')
199204
expect(vm.b).toBe('d')
200205
expect(vm.c).toBe('cd')
201206
expect(vm.d).toBe('cd')
207+
expect(vm.e).toBe('cde')
208+
Vue.nextTick(function () {
209+
expect(spy).toHaveBeenCalledWith('cde', 'abe')
210+
done()
211+
})
202212
})
203213

204-
it('inherit', function () {
214+
it('inherit', function (done) {
205215
var child = vm.$addChild({
206216
inherit: true
207217
})
@@ -212,10 +222,16 @@ describe('Instance Scope', function () {
212222
expect(vm.b).toBe('f')
213223
expect(vm.c).toBe('ef')
214224
expect(vm.d).toBe('ef')
225+
expect(vm.e).toBe('efe')
215226
expect(child.a).toBe('e')
216227
expect(child.b).toBe('f')
217228
expect(child.c).toBe('ef')
218229
expect(child.d).toBe('ef')
230+
expect(vm.e).toBe('efe')
231+
Vue.nextTick(function () {
232+
expect(spy).toHaveBeenCalledWith('efe', 'cde')
233+
done()
234+
})
219235
})
220236

221237
it('same definition object bound to different instance', function () {
@@ -232,6 +248,7 @@ describe('Instance Scope', function () {
232248
expect(vm.b).toBe('D')
233249
expect(vm.c).toBe('CD')
234250
expect(vm.d).toBe('CD')
251+
expect(vm.e).toBe('CDe')
235252
})
236253

237254
})

test/unit/specs/observer/observer_spec.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var Observer = require('../../../../src/observer')
2+
var Dep = require('../../../../src/observer/dep')
23
var config = require('../../../../src/config')
34
var _ = require('../../../../src/util')
45

@@ -59,19 +60,19 @@ describe('Observer', function () {
5960
update: jasmine.createSpy()
6061
}
6162
// collect dep
62-
Observer.setTarget(watcher)
63+
Dep.target = watcher
6364
obj.a.b
64-
Observer.setTarget(null)
65+
Dep.target = null
6566
expect(watcher.deps.length).toBe(3) // obj.a + a.b + b
6667
obj.a.b = 3
6768
expect(watcher.update.calls.count()).toBe(1)
6869
// swap object
6970
obj.a = { b: 4 }
7071
expect(watcher.update.calls.count()).toBe(2)
7172
watcher.deps = []
72-
Observer.setTarget(watcher)
73+
Dep.target = watcher
7374
obj.a.b
74-
Observer.setTarget(null)
75+
Dep.target = null
7576
expect(watcher.deps.length).toBe(3)
7677
// set on the swapped object
7778
obj.a.b = 5

test/unit/specs/watcher_spec.js

+21
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,27 @@ describe('Watcher', function () {
329329
})
330330
})
331331

332+
it('lazy mode', function (done) {
333+
var watcher = new Watcher(vm, function () {
334+
return this.a + this.b.d
335+
}, null, { lazy: true })
336+
expect(watcher.lazy).toBe(true)
337+
expect(watcher.value).toBeUndefined()
338+
expect(watcher.dirty).toBe(true)
339+
watcher.evaluate()
340+
expect(watcher.value).toBe(5)
341+
expect(watcher.dirty).toBe(false)
342+
vm.a = 2
343+
nextTick(function () {
344+
expect(watcher.value).toBe(5)
345+
expect(watcher.dirty).toBe(true)
346+
watcher.evaluate()
347+
expect(watcher.value).toBe(6)
348+
expect(watcher.dirty).toBe(false)
349+
done()
350+
})
351+
})
352+
332353
it('teardown', function (done) {
333354
var watcher = new Watcher(vm, 'b.c', spy)
334355
watcher.teardown()

0 commit comments

Comments
 (0)