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/events.js",
"src/api/global.js",
"src/api/lifecycle.js",
"src/batcher.js",
"src/binding.js",
"src/cache.js",
@ -44,8 +43,10 @@
"src/filters/array-filters.js",
"src/filters/index.js",
"src/instance/bindings.js",
"src/instance/children.js",
"src/instance/events.js",
"src/instance/init.js",
"src/instance/lifecycle.js",
"src/instance/scope.js",
"src/observe/array-augmentations.js",
"src/observe/object-augmentations.js",

View File

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

View File

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

View File

@ -16,14 +16,15 @@ exports._init = function (options) {
options = options || {}
this.$el = null
this.$ = {}
this.$root = this.$root || this
this._data = options.data || {}
this._emitter = new Emitter(this)
this._watchers = {}
this._directives = []
this._rawContent = null
this.$el = null
this.$ = {}
this.$root = this.$root || this
this._data = options.data || {}
this._emitter = new Emitter(this)
this._watchers = {}
this._userWatchers = {}
this._directives = []
this._rawContent = null
this._activeWatcher = null
// block instance properties

View File

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

View File

@ -15,7 +15,7 @@ var uid = 0
* @param {Vue} vm
* @param {String} expression
* @param {Function} cb
* @param {Object} [ctx]
* @oaram {Object} ctx
* @param {Array} [filters]
* @param {Boolean} [needSet]
* @constructor
@ -24,8 +24,8 @@ var uid = 0
function Watcher (vm, expression, cb, ctx, filters, needSet) {
this.vm = vm
this.expression = expression
this.cb = cb // change callback
this.ctx = ctx || vm // change callback context
this.cbs = [cb]
this.ctxs = [ctx]
this.id = ++uid // uid for batching
this.value = undefined
this.active = true
@ -173,11 +173,46 @@ p.run = function () {
) {
var oldValue = this.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()
}
}
/**
* Remove self from all dependencies' subcriber list.
*/
@ -189,6 +224,7 @@ p.teardown = function () {
for (var path in this.deps) {
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) {
var watcher = new Watcher(vm, 'b.c', spy)
watcher.teardown()
vm.b.c = 3
nextTick(function () {
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)
done()
})