From 36599833de1e095f22ec4d9dd083436069f64a54 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 2 Sep 2014 08:35:34 -0700 Subject: [PATCH] further simplify observe --- src/api/data.js | 2 + src/api/lifecycle.js | 2 - src/instance/init.js | 1 + src/instance/scope.js | 48 ++++++++--------- src/observer/array.js | 22 ++------ src/observer/index.js | 91 +++++---------------------------- src/observer/object.js | 3 +- src/watcher.js | 3 ++ test/unit/specs/watcher_spec.js | 16 +++--- 9 files changed, 51 insertions(+), 137 deletions(-) diff --git a/src/api/data.js b/src/api/data.js index f1bb0841a..c4777bade 100644 --- a/src/api/data.js +++ b/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() } } diff --git a/src/api/lifecycle.js b/src/api/lifecycle.js index 017f56f05..8d52c72aa 100644 --- a/src/api/lifecycle.js +++ b/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 = diff --git a/src/instance/init.js b/src/instance/init.js index 347788e5c..0ab784321 100644 --- a/src/instance/init.js +++ b/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 = [] diff --git a/src/instance/scope.js b/src/instance/scope.js index 6c886b27d..61de397e4 100644 --- a/src/instance/scope.js +++ b/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 diff --git a/src/observer/array.js b/src/observer/array.js index a542d7ede..5feb5ea26 100644 --- a/src/observer/array.js +++ b/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 diff --git a/src/observer/index.js b/src/observer/index.js index cfd4f4f2d..a49d5ec71 100644 --- a/src/observer/index.js +++ b/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 \ No newline at end of file diff --git a/src/observer/object.js b/src/observer/object.js index 0c73b3f3a..3f1d23a46 100644 --- a/src/observer/object.js +++ b/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) { diff --git a/src/watcher.js b/src/watcher.js index da31aa608..aa9daf840 100644 --- a/src/watcher.js +++ b/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) } diff --git a/test/unit/specs/watcher_spec.js b/test/unit/specs/watcher_spec.js index ac754a09b..16e73b39c 100644 --- a/test/unit/specs/watcher_spec.js +++ b/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() }) })