watcher multiple callbacks

This commit is contained in:
Evan You 2014-08-28 19:02:32 -04:00
parent cd8d44e5b4
commit 0886716b2d
7 changed files with 145 additions and 39 deletions

View File

@ -15,7 +15,6 @@
"src/api/dom.js", "src/api/dom.js",
"src/api/events.js", "src/api/events.js",
"src/api/global.js", "src/api/global.js",
"src/api/lifecycle.js",
"src/batcher.js", "src/batcher.js",
"src/binding.js", "src/binding.js",
"src/cache.js", "src/cache.js",
@ -44,8 +43,10 @@
"src/filters/array-filters.js", "src/filters/array-filters.js",
"src/filters/index.js", "src/filters/index.js",
"src/instance/bindings.js", "src/instance/bindings.js",
"src/instance/children.js",
"src/instance/events.js", "src/instance/events.js",
"src/instance/init.js", "src/instance/init.js",
"src/instance/lifecycle.js",
"src/instance/scope.js", "src/instance/scope.js",
"src/observe/array-augmentations.js", "src/observe/array-augmentations.js",
"src/observe/object-augmentations.js", "src/observe/object-augmentations.js",

View File

@ -62,35 +62,41 @@ exports.$delete = function (key) {
/** /**
* Watch an expression, trigger callback when its * Watch an expression, trigger callback when its
* value changes. Returns the created watcher's * value changes.
* id so it can be teardown later.
* *
* @param {String} exp * @param {String} exp
* @param {Function} cb * @param {Function} cb
* @param {Boolean} [immediate] * @param {Boolean} [immediate]
* @return {Number}
*/ */
exports.$watch = function (exp, cb, immediate) { exports.$watch = function (exp, cb, immediate) {
var watcher = new Watcher(this, exp, cb, this) var watcher = this._userWatchers[exp]
this._watchers[watcher.id] = watcher if (!watcher) {
watcher =
this._userWatchers[exp] =
new Watcher(this, exp, cb, this)
} else {
watcher.addCb(cb, this)
}
if (immediate) { if (immediate) {
cb.call(this, watcher.value) cb.call(this, watcher.value)
} }
return watcher.id
} }
/** /**
* Teardown a watcher with given id. * Teardown a watcher with given id.
* *
* @param {Number} id * @param {String} exp
* @param {Function} cb
*/ */
exports.$unwatch = function (id) { exports.$unwatch = function (exp, cb) {
var watcher = this._watchers[id] var watcher = this._userWatchers[exp]
if (watcher) { if (watcher) {
watcher.teardown() watcher.removeCb(cb)
this._watchers[id] = null if (!watcher.active) {
this._userWatchers[exp] = null
}
} }
} }

View File

@ -47,7 +47,11 @@ var p = Directive.prototype
*/ */
p._bind = function (def) { p._bind = function (def) {
if (typeof def === 'function') {
this.update = def
} else {
_.extend(this, def) _.extend(this, def)
}
this._watcherExp = this.expression this._watcherExp = this.expression
this._checkDynamicLiteral() this._checkDynamicLiteral()
if (this.bind) { if (this.bind) {
@ -58,15 +62,22 @@ p._bind = function (def) {
(!this.isLiteral || this._isDynamicLiteral) (!this.isLiteral || this._isDynamicLiteral)
) { ) {
if (!this._checkExpFn()) { if (!this._checkExpFn()) {
this._watcher = new Watcher( var exp = this._watcherExp
var wathcer = this.vm._watchers[exp]
if (!wathcer) {
watcher = this.vm._watchers[exp] = new Watcher(
this.vm, this.vm,
this._watcherExp, exp,
this._update, // callback this._update, // callback
this, // callback context this, // callback context
this.filters, this.filters,
this.twoWay // need setter this.twoWay // need setter
) )
this.update(this._watcher.value) } else {
watcher.addCb(this._update, this)
}
this._watcher = watcher
this.update(watcher.value)
} }
} }
this._bound = true this._bound = true
@ -149,10 +160,15 @@ p._teardown = function () {
if (this.unbind) { if (this.unbind) {
this.unbind() this.unbind()
} }
if (this._watcher) { var watcher = this._watcher
this._watcher.teardown() if (watcher) {
watcher.removeCb(this._update)
if (!watcher.active) {
this.vm._watchers[this.expression] = null
}
} }
this._bound = false this._bound = false
this.vm = this.el = null
} }
} }

View File

@ -22,6 +22,7 @@ exports._init = function (options) {
this._data = options.data || {} this._data = options.data || {}
this._emitter = new Emitter(this) this._emitter = new Emitter(this)
this._watchers = {} this._watchers = {}
this._userWatchers = {}
this._directives = [] this._directives = []
this._rawContent = null this._rawContent = null
this._activeWatcher = null this._activeWatcher = null

View File

@ -107,9 +107,12 @@ exports.$destroy = function (remove) {
} }
// teardown data/scope // teardown data/scope
this._teardownScope() this._teardownScope()
// teardown all user watchers // teardown all watchers
for (var id in this._watchers) { for (i in this._watchers) {
this.$unwatch(id) this._watchers[i].teardown()
}
for (i in this._userWatchers) {
this._userWatchers[i].teardown()
} }
// teardown all directives // teardown all directives
i = this._directives.length i = this._directives.length
@ -119,6 +122,7 @@ exports.$destroy = function (remove) {
// clean up // clean up
this._children = this._children =
this._watchers = this._watchers =
this._userWatchers =
this._activeWatcher = this._activeWatcher =
this.$el = this.$el =
this.$el.__vue__ = this.$el.__vue__ =

View File

@ -15,7 +15,7 @@ var uid = 0
* @param {Vue} vm * @param {Vue} vm
* @param {String} expression * @param {String} expression
* @param {Function} cb * @param {Function} cb
* @param {Object} [ctx] * @oaram {Object} ctx
* @param {Array} [filters] * @param {Array} [filters]
* @param {Boolean} [needSet] * @param {Boolean} [needSet]
* @constructor * @constructor
@ -24,8 +24,8 @@ var uid = 0
function Watcher (vm, expression, cb, ctx, filters, needSet) { function Watcher (vm, expression, cb, ctx, filters, needSet) {
this.vm = vm this.vm = vm
this.expression = expression this.expression = expression
this.cb = cb // change callback this.cbs = [cb]
this.ctx = ctx || vm // change callback context this.ctxs = [ctx]
this.id = ++uid // uid for batching this.id = ++uid // uid for batching
this.value = undefined this.value = undefined
this.active = true this.active = true
@ -173,9 +173,44 @@ p.run = function () {
) { ) {
var oldValue = this.value var oldValue = this.value
this.value = value this.value = value
this.cb.call(this.ctx, value, oldValue) var cbs = this.cbs
var ctxs = this.ctxs
for (var i = 0, l = cbs.length; i < l; i++) {
cbs[i].call(ctxs[i], value, oldValue)
} }
} }
}
}
/**
* Add a callback.
*
* @param {Function} cb
* @param {Object} ctx
*/
p.addCb = function (cb, ctx) {
this.cbs.push(cb)
this.ctxs.push(ctx)
}
/**
* Remove a callback.
*
* @param {Function} cb
*/
p.removeCb = function (cb) {
var cbs = this.cbs
if (cbs.length > 1) {
var i = cbs.indexOf(cb)
if (i > -1) {
cbs.splice(i, 1)
this.ctxs.splice(i, 1)
}
} else if (cb === cbs[0]) {
this.teardown()
}
} }
/** /**
@ -189,6 +224,7 @@ p.teardown = function () {
for (var path in this.deps) { for (var path in this.deps) {
vm._bindings[path]._removeSub(this) vm._bindings[path]._removeSub(this)
} }
this.vm = this.cbs = this.ctxs = null
} }
} }

View File

@ -265,12 +265,54 @@ describe('Watcher', function () {
}) })
}) })
it('add callback', function (done) {
var ctx1 = {}
var ctx2 = {}
var watcher = new Watcher(vm, 'a', function () {
this.called = 1
}, ctx1)
watcher.addCb(function () {
this.called = 2
}, ctx2)
vm.a = 99
nextTick(function () {
expect(ctx1.called).toBe(1)
expect(ctx2.called).toBe(2)
done()
})
})
it('remove callback', function (done) {
// single, should equal teardown
var fn = function () {}
var watcher = new Watcher(vm, 'a', fn)
watcher.removeCb(fn)
expect(watcher.active).toBe(false)
expect(watcher.vm).toBe(null)
expect(watcher.cbs).toBe(null)
expect(watcher.ctxs).toBe(null)
// multiple
watcher = new Watcher(vm, 'a', spy)
var spy2 = jasmine.createSpy()
watcher.addCb(spy2)
watcher.removeCb(spy)
vm.a = 234
nextTick(function () {
expect(spy.calls.count()).toBe(0)
expect(spy2).toHaveBeenCalledWith(234, 1)
done()
})
})
it('teardown', function (done) { it('teardown', function (done) {
var watcher = new Watcher(vm, 'b.c', spy) var watcher = new Watcher(vm, 'b.c', spy)
watcher.teardown() watcher.teardown()
vm.b.c = 3 vm.b.c = 3
nextTick(function () { nextTick(function () {
expect(watcher.active).toBe(false) expect(watcher.active).toBe(false)
expect(watcher.vm).toBe(null)
expect(watcher.cbs).toBe(null)
expect(watcher.ctxs).toBe(null)
expect(spy.calls.count()).toBe(0) expect(spy.calls.count()).toBe(0)
done() done()
}) })