Przeglądaj źródła

observer memory management

Evan You 11 lat temu
rodzic
commit
4fdce129da

+ 7 - 5
src/api/dom.js

@@ -73,13 +73,15 @@ exports.$after = function (target, cb, withTransition) {
  */
 
 exports.$remove = function (cb, withTransition) {
+  var canRemove = this._isAttached && _.inDoc(this.$el)
+  if (!canRemove) {
+    if (cb) cb()
+    return
+  }
   var op
-  var shouldCallHook = this._isAttached && _.inDoc(this.$el)
   var self = this
   var realCb = function () {
-    if (shouldCallHook) {
-      self._callHook('detached')
-    }
+    self._callHook('detached')
     if (cb) cb()
   }
   if (
@@ -90,7 +92,7 @@ exports.$remove = function (cb, withTransition) {
       ? _.append
       : transition.removeThenAppend 
     blockOp(this, this._blockFragment, op, realCb)
-  } else if (this.$el.parentNode) {
+  } else {
     op = withTransition === false
       ? _.remove
       : transition.remove

+ 1 - 2
src/instance/init.js

@@ -24,7 +24,6 @@ exports._init = function (options) {
   this._watchers      = {}
   this._userWatchers  = {}
   this._directives    = []
-  this._rawContent    = null
   this._activeWatcher = null
 
   // block instance properties
@@ -61,7 +60,7 @@ exports._init = function (options) {
   this._initScope()
 
   // setup binding tree.
-  // @creates this._rootBinding
+  // @creates this._bindings
   this._initBindings()
 
   // setup event system and option events

+ 6 - 1
src/instance/lifecycle.js

@@ -118,16 +118,21 @@ exports.$destroy = function (remove) {
     this._directives[i]._teardown()
   }
   // clean up
-  this._children =
+  this._data =
   this._watchers =
   this._userWatchers =
   this._activeWatcher =
   this.$el =
   this.$el.__vue__ =
+  this.$parent =
+  this.$observer =
+  this._children =
+  this._bindings =
   this._directives = null
   // call the last hook...
   this._isDestroyed = true
   this._callHook('afterDestroy')
   // turn off all instance listeners.
   this._emitter.off()
+  this._emitter = null
 }

+ 8 - 1
src/instance/scope.js

@@ -47,8 +47,10 @@ exports._teardownScope = function () {
     event = dataEvents[i]
     dataOb.off(event, proxies[event])
   }
+  dataOb.vmOwnerCount--
+  dataOb.tryRelease()
   // unset data reference
-  this._data = null
+  this._data = this._dataProxies = null
 }
 
 /**
@@ -88,6 +90,7 @@ exports._initData = function () {
   }
   // relay data changes
   var ob = Observer.create(data)
+  ob.vmOwnerCount++
   var proxies = this._dataProxies
   var event
   i = dataEvents.length
@@ -147,6 +150,10 @@ exports._setData = function (newData) {
     newOb.on(event, proxy)
     oldOb.off(event, proxy)
   }
+  newOb.vmOwnerCount++
+  oldOb.vmOwnerCount--
+  // memory managment, important!
+  oldOb.tryRelease()
 }
 
 /**

+ 48 - 4
src/observe/observer.js

@@ -35,6 +35,7 @@ function Observer (value, type) {
   this.type = type
   this.parents = null
   this.parentsHash = null
+  this.vmOwnerCount = 0
   if (value) {
     _.define(value, '__ob__', this)
     if (type === ARRAY) {
@@ -78,7 +79,11 @@ Observer.emitGet = false
  */
 
 Observer.create = function (value, options) {
-  if (value && value.hasOwnProperty('__ob__')) {
+  if (
+    value &&
+    value.hasOwnProperty('__ob__') &&
+    value.__ob__ instanceof Observer
+  ) {
     return value.__ob__
   } else if (_.isArray(value)) {
     return new Observer(value, ARRAY, options)
@@ -181,9 +186,10 @@ p.observe = function (key, val) {
  */
 
 p.unobserve = function (val) {
-  if (val && val.__ob__) {
-    val.__ob__.parentsHash[this.id] = null
-    var parents = val.__ob__.parents
+  var ob = val && val.__ob__
+  if (ob) {
+    ob.parentsHash[this.id] = null
+    var parents = ob.parents
     var i = parents.length
     while (i--) {
       if (parents[i].ob === this) {
@@ -191,6 +197,7 @@ p.unobserve = function (val) {
         break
       }
     }
+    ob.tryRelease()
   }
 }
 
@@ -268,4 +275,41 @@ p.updateIndices = function () {
   }
 }
 
+/**
+ * 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.parents || !this.parents.length) &&
+    !this.vmOwnerCount
+  ) {
+    var value = this.value
+    value.__ob__ = null
+    this.parents =
+    this.parentsHash =
+    this._cbs =
+    this.value = null
+    if (_.isArray(value)) {
+      this.unlink(value)
+    } else {
+      for (var key in value) {
+        var val = value[key]
+        this.unobserve(val)
+        // release the getter/setter closures
+        _.define(value, key, val, true)
+      }
+    }
+  }
+}
+
 module.exports = Observer

+ 2 - 2
src/watcher.js

@@ -213,12 +213,12 @@ p.removeCb = function (cb) {
 
 p.teardown = function () {
   if (this.active) {
-    this.active = false
     var vm = this.vm
     for (var path in this.deps) {
       vm._bindings[path]._removeSub(this)
     }
-    this.vm = this.cbs = null
+    this.active = false
+    this.vm = this.cbs = this.value = null
   }
 }