Explorar o código

watcher multiple callbacks

Evan You %!s(int64=11) %!d(string=hai) anos
pai
achega
0886716b2d
Modificáronse 7 ficheiros con 145 adicións e 39 borrados
  1. 2 1
      component.json
  2. 17 11
      src/api/data.js
  3. 28 12
      src/directive.js
  4. 9 8
      src/instance/init.js
  5. 7 3
      src/instance/lifecycle.js
  6. 40 4
      src/watcher.js
  7. 42 0
      test/unit/specs/watcher_spec.js

+ 2 - 1
component.json

@@ -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",

+ 17 - 11
src/api/data.js

@@ -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
+    }
   }
 }
 

+ 28 - 12
src/directive.js

@@ -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
   }
 }
 

+ 9 - 8
src/instance/init.js

@@ -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

+ 7 - 3
src/instance/lifecycle.js

@@ -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__ =

+ 40 - 4
src/watcher.js

@@ -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
   }
 }
 

+ 42 - 0
test/unit/specs/watcher_spec.js

@@ -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()
     })