Skip to content

Commit

Permalink
observer rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 21, 2013
1 parent 070d5c0 commit 216e398
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 111 deletions.
196 changes: 97 additions & 99 deletions src/observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,14 @@ for (var method in extensions) {
def(ArrayProxy, method, extensions[method], !hasProto)
}

/**
* Watch an object based on type
*/
function watch (obj, path, observer) {
var type = typeOf(obj)
if (type === 'Object') {
watchObject(obj, path, observer)
} else if (type === 'Array') {
watchArray(obj, path, observer)
}
}

/**
* Watch an Object, recursive.
*/
function watchObject (obj, path, observer) {
for (var key in obj) {
var keyPrefix = key.charAt(0)
if (keyPrefix !== '$' && keyPrefix !== '_') {
bind(obj, key, path, observer)
convert(obj, key, observer)
}
}
}
Expand All @@ -131,33 +119,34 @@ function watchArray (arr, path, observer) {
* so it emits get/set events.
* Then watch the value itself.
*/
function bind (obj, key, path, observer) {
function convert (obj, key, observer) {
var val = obj[key],
watchable = isWatchable(val),
values = observer.values,
fullKey = (path ? path + '.' : '') + key
values[fullKey] = val
values = observer.values
values[key] = val
// emit set on bind
// this means when an object is observed it will emit
// a first batch of set events.
observer.emit('set', fullKey, val)
observer.emit('set', key, val)
Object.defineProperty(obj, key, {
enumerable: true,
get: function () {
var value = values[key]
// only emit get on tip values
if (depsOb.active && !watchable) {
observer.emit('get', fullKey)
if (depsOb.active && !isWatchable(value)) {
observer.emit('get', key)
}
return values[fullKey]
return value
},
set: function (newVal) {
values[fullKey] = newVal
ensurePaths(key, newVal, values)
observer.emit('set', fullKey, newVal)
watch(newVal, fullKey, observer)
var oldVal = values[key]
unobserve(oldVal, key, observer)
values[key] = newVal
ensurePaths('', newVal, oldVal)
observer.emit('set', key, newVal)
observe(newVal, key, observer)
}
})
watch(val, fullKey, observer)
observe(val, key, observer)
}

/**
Expand All @@ -175,14 +164,17 @@ function isWatchable (obj) {
* the watch conversion and simply emit set event for
* all of its properties.
*/
function emitSet (obj, observer, set) {
if (typeOf(obj) === 'Array') {
set('length', obj.length)
} else {
var key, val, values = observer.values
for (key in observer.values) {
val = values[key]
set(key, val)
function emitSet (obj) {
var type = typeOf(obj),
emitter = obj.__observer__
if (type === 'Array') {
emitter.emit('set', 'length', obj.length)
} else if (type === 'Object') {
var key, val
for (key in obj) {
val = obj[key]
emitter.emit('set', key, val)
emitSet(val)
}
}
}
Expand All @@ -194,10 +186,10 @@ function emitSet (obj, observer, set) {
* any given path.
*/
function ensurePaths (key, val, paths) {
key += '.'
key = key ? key + '.' : ''
for (var path in paths) {
if (!path.indexOf(key)) {
ensurePath(val, path.replace(key, ''))
if (!key || !path.indexOf(key)) {
ensurePath(val, key ? path.replace(key, '') : path)
}
}
}
Expand All @@ -222,68 +214,74 @@ function ensurePath (obj, key) {
return obj[sec]
}

module.exports = {

// used in v-repeat
watchArray: watchArray,
ensurePath: ensurePath,
ensurePaths: ensurePaths,

/**
* Observe an object with a given path,
* and proxy get/set/mutate events to the provided observer.
*/
observe: function (obj, rawPath, observer) {
if (isWatchable(obj)) {
var path = rawPath + '.',
ob, alreadyConverted = !!obj.__observer__
if (!alreadyConverted) {
def(obj, '__observer__', new Emitter())
}
ob = obj.__observer__
ob.values = ob.values || utils.hash()
var proxies = observer.proxies[path] = {
get: function (key) {
observer.emit('get', path + key)
},
set: function (key, val) {
observer.emit('set', path + key, val)
},
mutate: function (key, val, mutation) {
// if the Array is a root value
// the key will be null
var fixedPath = key ? path + key : rawPath
observer.emit('mutate', fixedPath, val, mutation)
// also emit set for Array's length when it mutates
var m = mutation.method
if (m !== 'sort' && m !== 'reverse') {
observer.emit('set', fixedPath + '.length', val.length)
}
}
}
ob
.on('get', proxies.get)
.on('set', proxies.set)
.on('mutate', proxies.mutate)
if (alreadyConverted) {
emitSet(obj, ob, proxies.set)
} else {
watch(obj, null, ob)
/**
* Observe an object with a given path,
* and proxy get/set/mutate events to the provided observer.
*/
function observe (obj, rawPath, observer) {
if (!isWatchable(obj)) return
var path = rawPath ? rawPath + '.' : '',
ob, alreadyConverted = !!obj.__observer__
if (!alreadyConverted) {
def(obj, '__observer__', new Emitter())
}
ob = obj.__observer__
ob.values = ob.values || utils.hash()
observer.proxies = observer.proxies || {}
var proxies = observer.proxies[path] = {
get: function (key) {
observer.emit('get', path + key)
},
set: function (key, val) {
observer.emit('set', path + key, val)
},
mutate: function (key, val, mutation) {
// if the Array is a root value
// the key will be null
var fixedPath = key ? path + key : rawPath
observer.emit('mutate', fixedPath, val, mutation)
// also emit set for Array's length when it mutates
var m = mutation.method
if (m !== 'sort' && m !== 'reverse') {
observer.emit('set', fixedPath + '.length', val.length)
}
}
},

/**
* Cancel observation, turn off the listeners.
*/
unobserve: function (obj, path, observer) {
if (!obj || !obj.__observer__) return
path = path + '.'
var proxies = observer.proxies[path]
obj.__observer__
.off('get', proxies.get)
.off('set', proxies.set)
.off('mutate', proxies.mutate)
observer.proxies[path] = null
}
ob
.on('get', proxies.get)
.on('set', proxies.set)
.on('mutate', proxies.mutate)
if (alreadyConverted) {
emitSet(obj)
} else {
var type = typeOf(obj)
if (type === 'Object') {
watchObject(obj, null, ob)
} else if (type === 'Array') {
watchArray(obj, null, ob)
}
}
}

/**
* Cancel observation, turn off the listeners.
*/
function unobserve (obj, path, observer) {
if (!obj || !obj.__observer__) return
path = path + '.'
var proxies = observer.proxies[path]
obj.__observer__
.off('get', proxies.get)
.off('set', proxies.set)
.off('mutate', proxies.mutate)
observer.proxies[path] = null
}

module.exports = {
observe : observe,
unobserve : unobserve,
ensurePath : ensurePath,
ensurePaths : ensurePaths,
// used in v-repeat
watchArray : watchArray,
}
13 changes: 1 addition & 12 deletions test/unit/specs/observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ describe('UNIT: Observer', function () {

it('should not watch a ViewModel instance', function () {
var obj = new Vue(), ob = new Emitter()
ob.proxies = {}
Observer.observe(obj, 'test', ob)
assert.notOk(obj.__observer__)
})

it('should attach hidden observer and values to the object', function () {
var obj = {}, ob = new Emitter()
ob.proxies = {}
Observer.observe(obj, 'test', ob)
assert.ok(obj.__observer__ instanceof Emitter)
assert.ok(obj.__observer__.values)
Expand Down Expand Up @@ -55,7 +53,6 @@ describe('UNIT: Observer', function () {
it('should emit set when first observing', function () {
var obj = { a: 1, b: { c: 2} },
ob = new Emitter(), i = 0
ob.proxies = {}
var expects = [
{ key: 'test.a', val: obj.a },
{ key: 'test.b', val: obj.b },
Expand All @@ -76,8 +73,6 @@ describe('UNIT: Observer', function () {
ob1 = new Emitter(),
ob2 = new Emitter(),
i = 0
ob1.proxies = {}
ob2.proxies = {}
Observer.observe(obj, 'test', ob1) // watch first time

var expects = [
Expand All @@ -86,6 +81,7 @@ describe('UNIT: Observer', function () {
{ key: 'test.b.c', val: obj.b.c }
]
ob2.on('set', function (key, val) {
console.log(key)
var exp = expects[i]
assert.strictEqual(key, exp.key)
assert.strictEqual(val, exp.val)
Expand All @@ -101,7 +97,6 @@ describe('UNIT: Observer', function () {

var arr = [],
ob = new Emitter()
ob.proxies = {}
Observer.observe(arr, 'test', ob)

it('should attach the hidden observer', function () {
Expand Down Expand Up @@ -371,8 +366,6 @@ describe('UNIT: Observer', function () {
var ob1 = new Emitter(),
ob2 = new Emitter(),
obj = {a:1}
ob1.proxies = {}
ob2.proxies = {}
Observer.observe(obj, 'test', ob1)
Observer.observe(obj, 'test', ob2)

Expand All @@ -399,8 +392,6 @@ describe('UNIT: Observer', function () {
var ob1 = new Emitter(),
ob2 = new Emitter(),
obj = {a:1}
ob1.proxies = {}
ob2.proxies = {}
Observer.observe(obj, 'test', ob1)
Observer.observe(obj, 'test', ob2)
Observer.unobserve(obj, 'test', ob1)
Expand Down Expand Up @@ -470,7 +461,6 @@ describe('UNIT: Observer', function () {
i = 0,
obj = opts.obj,
expects = opts.expects
ob.proxies = {}
Observer.observe(obj, opts.path, ob)
ob.on('set', function (key, val) {
var expect = expects[i]
Expand Down Expand Up @@ -499,7 +489,6 @@ describe('UNIT: Observer', function () {
i = 0,
obj = opts.obj,
expects = opts.expects
ob.proxies = {}
Observer.observe(obj, opts.path, ob)
ob.on('get', function (key) {
var expected = expects[i]
Expand Down

0 comments on commit 216e398

Please sign in to comment.