further simplify observe

This commit is contained in:
Evan You 2014-09-02 08:35:34 -07:00
parent 1dfb10e491
commit 36599833de
9 changed files with 51 additions and 137 deletions

View File

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

View File

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

View File

@ -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 = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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