Evan You пре 11 година
родитељ
комит
36599833de

+ 2 - 0
src/api/data.js

@@ -46,6 +46,7 @@ exports.$add = function (key, val) {
   if (!_.isReserved(key)) {
     this._data.$add(key, val)
     this._proxy(key)
+    this._digest()
   }
 }
 
@@ -59,6 +60,7 @@ exports.$delete = function (key) {
   if (!_.isReserved(key)) {
     this._data.$delete(key)
     this._unproxy(key)
+    this._digest()
   }
 }
 

+ 0 - 2
src/api/lifecycle.js

@@ -115,8 +115,6 @@ exports.$destroy = function (remove) {
   for (i in this._userWatchers) {
     this._userWatchers[i].teardown()
   }
-  // teardown data/scope
-  this._teardownScope()
   // clean up
   this._data =
   this._watchers =

+ 1 - 0
src/instance/init.js

@@ -18,6 +18,7 @@ exports._init = function (options) {
   this.$el           = null
   this.$root         = this.$root || this
   this.$             = {}
+  this._watcherList  = []
   this._watchers     = {}
   this._userWatchers = {}
   this._directives   = []

+ 20 - 28
src/instance/scope.js

@@ -23,18 +23,6 @@ exports._initScope = function () {
   this._initMeta()
 }
 
-/**
- * Teardown the scope.
- */
-
-exports._teardownScope = function () {
-  var dataOb = this._data.__ob__
-  dataOb.vmCount--
-  dataOb.tryRelease()
-  // unset data reference
-  this._data = null
-}
-
 /**
  * Initialize the data. 
  */
@@ -48,8 +36,7 @@ exports._initData = function () {
     this._proxy(keys[i])
   }
   // observe data
-  var ob = Observer.create(data)
-  ob.vmCount++
+  Observer.create(data)
 }
 
 /**
@@ -82,20 +69,8 @@ exports._setData = function (newData) {
       this._proxy(key)
     }
   }
-  // observe new / teardown old
-  var newOb = Observer.create(newData)
-  var oldOb = oldData.__ob__
-  newOb.vmCount++
-  oldOb.vmCount--
-  // memory managment, important!
-  oldOb.tryRelease()
-  // update ALL watchers
-  for (key in this._watchers) {
-    this._watchers[key].update()
-  }
-  for (key in this._userWatchers) {
-    this._userWatchers[key].update()
-  }
+  Observer.create(newData)
+  this._digest()
 }
 
 /**
@@ -134,6 +109,23 @@ exports._unproxy = function (key) {
   delete this[key]
 }
 
+/**
+ * Force update on every watcher in scope.
+ */
+
+exports._digest = function () {
+  var i = this._watcherList.length
+  while (i--) {
+    this._watcherList[i].update()
+  }
+  if (this._children) {
+    i = this._children.length
+    while (i--) {
+      this._children[i]._digest()
+    }
+  }
+}
+
 /**
  * Setup computed properties. They are essentially
  * special getter/setters

+ 3 - 19
src/observer/array.js

@@ -28,8 +28,7 @@ var arrayAugmentations = Object.create(Array.prototype)
     }
     var result = original.apply(this, args)
     var ob = this.__ob__
-    var inserted, removed
-
+    var inserted
     switch (method) {
       case 'push':
         inserted = args
@@ -37,28 +36,13 @@ var arrayAugmentations = Object.create(Array.prototype)
       case 'unshift':
         inserted = args
         break
-      case 'pop':
-        removed = [result]
-        break
-      case 'shift':
-        removed = [result]
-        break
       case 'splice':
         inserted = args.slice(2)
-        removed = result
         break
     }
-
-    // link/unlink added/removed elements
     if (inserted) ob.observeArray(inserted)
-    if (removed) ob.unobserveArray(removed)
-
-    // notify bindings
-    i = ob.bindings.length
-    while (i--) {
-      ob.bindings[i].notify()
-    }
-
+    // notify change
+    ob.binding.notify()
     return result
   }
   // define wrapped method

+ 14 - 77
src/observer/index.js

@@ -27,13 +27,12 @@ function Observer (value, type) {
   this.id = ++uid
   this.value = value
   this.type = type
-  this.parentCount = 0
-  this.vmCount = 0
+  this.active = true
+  this.binding = new Binding()
   if (value) {
     _.define(value, '__ob__', this)
     if (type === ARRAY) {
       _.augment(value, arrayAugmentations)
-      this.bindings = []
       this.observeArray(value)
     } else if (type === OBJECT) {
       _.augment(value, objectAugmentations)
@@ -100,35 +99,13 @@ p.walk = function (obj) {
  * and if value is array, link binding to the array.
  *
  * @param {*} val
- * @param {Binding} [binding]
  */
 
-p.observe = function (val, binding) {
+p.observe = function (val) {
   var ob = Observer.create(val)
   if (ob) {
-    ob.parentCount++
-    if (binding && ob.type === ARRAY) {
-      ob.bindings.push(binding)
-    }
-  }
-}
-
-/**
- * Unobserve a value.
- *
- * @param {*} val
- * @param {Binding} [binding]
- */
-
-p.unobserve = function (val, binding) {
-  var ob = val && val.__ob__
-  if (ob) {
-    ob.parentCount--
-    if (binding && ob.type === ARRAY) {
-      var i = ob.bindings.indexOf(binding)
-      if (i > -1) ob.bindings.splice()
-    }
-    ob.tryRelease()
+    // ob.parentCount++
+    return ob.binding
   }
 }
 
@@ -145,19 +122,6 @@ p.observeArray = function (items) {
   }
 }
 
-/**
- * Unobserve a list of Array items.
- *
- * @param {Array} items
- */
-
-p.unobserveArray = function (items) {
-  var i = items.length
-  while (i--) {
-    this.unobserve(items[i])
-  }
-}
-
 /**
  * Convert a property into getter/setter so we can emit
  * the events when the property is accessed/changed.
@@ -168,58 +132,31 @@ p.unobserveArray = function (items) {
 
 p.convert = function (key, val) {
   var ob = this
-  var binding = new Binding()
-  ob.observe(val, binding)
+  var binding = ob.observe(val) || new Binding()
   Object.defineProperty(ob.value, key, {
     enumerable: true,
     configurable: true,
     get: function () {
       // Observer.target is a watcher whose getter is
       // currently being evaluated.
-      if (Observer.target) {
+      if (ob.active && Observer.target) {
         Observer.target.addDep(binding)
       }
       return val
     },
     set: function (newVal) {
       if (newVal === val) return
-      ob.unobserve(val, binding)
-      ob.observe(newVal, binding)
       val = newVal
+      var newBinding = ob.observe(newVal)
+      if (newBinding) {
+        // handle over binding
+        newBinding.subs = binding.subs
+        binding.subs = []
+        binding = newBinding
+      }
       binding.notify()
     }
   })
 }
 
-/**
- * Attempt to teardown the observer if the value is no
- * longer needed. Two requirements have to be met:
- *
- * 1. The observer has no parent obervers depending on it.
- * 2. The observer is not being used as the root $data by
- *    by a vm instance.
- *
- * This is important because each observer holds strong
- * reference to all its parents and if we don't do this
- * those parents can be leaked when a vm is destroyed.
- */
-
-p.tryRelease = function () {
-  if (!this.parentCount && !this.vmCount) {
-    var value = this.value
-    if (_.isArray(value)) {
-      value.__ob__.bindings = null
-      this.unobserveArray(value)
-    } else {
-      for (var key in value) {
-        var val = value[key]
-        this.unobserve(val)
-        // release closure
-        _.define(value, key, val, true)
-      }
-    }
-    value.__ob__ = null
-  }
-}
-
 module.exports = Observer

+ 2 - 1
src/observer/object.js

@@ -13,6 +13,7 @@ var objectAgumentations = Object.create(Object.prototype)
 function $add (key, val) {
   if (this.hasOwnProperty(key)) return
   this.__ob__.convert(key, val)
+  this.__ob__.binding.notify()
 }
 
 /**
@@ -25,8 +26,8 @@ function $add (key, val) {
 
 function $delete (key) {
   if (!this.hasOwnProperty(key)) return
-  this.__ob__.unobserve(this[key])
   delete this[key]
+  this.__ob__.binding.notify()
 }
 
 if (_.hasProto) {

+ 3 - 0
src/watcher.js

@@ -21,6 +21,7 @@ var uid = 0
 
 function Watcher (vm, expression, cb, filters, needSet) {
   this.vm = vm
+  vm._watcherList.push(this)
   this.expression = expression
   this.cbs = [cb]
   this.id = ++uid // uid for batching
@@ -173,6 +174,8 @@ p.removeCb = function (cb) {
 
 p.teardown = function () {
   if (this.active) {
+    var list = this.vm._watcherList
+    list.splice(list.indexOf(this))
     for (var id in this.deps) {
       this.deps[id].removeSub(this)
     }

+ 6 - 10
test/unit/specs/watcher_spec.js

@@ -167,16 +167,12 @@ describe('Watcher', function () {
     var oldData = vm.$data
     var watcher = new Watcher(vm, '$data', spy)
     expect(watcher.value).toBe(oldData)
-    vm.a = 2
-    nextTick(function () {
-      expect(spy).toHaveBeenCalledWith(oldData, oldData)
-      var newData = {}
-      vm.$data = newData
-      nextTick(function() {
-        expect(spy).toHaveBeenCalledWith(newData, oldData)
-        expect(watcher.value).toBe(newData)
-        done()
-      })
+    var newData = {}
+    vm.$data = newData
+    nextTick(function() {
+      expect(spy).toHaveBeenCalledWith(newData, oldData)
+      expect(watcher.value).toBe(newData)
+      done()
     })
   })