mirror of https://github.com/vuejs/vue.git
working on observer
This commit is contained in:
parent
55bfa2f2e7
commit
0ad7f54602
|
|
@ -11,6 +11,7 @@
|
||||||
"laxbreak": true,
|
"laxbreak": true,
|
||||||
"evil": true,
|
"evil": true,
|
||||||
"eqnull": true,
|
"eqnull": true,
|
||||||
|
"proto": true,
|
||||||
"globals": {
|
"globals": {
|
||||||
"console": true
|
"console": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
gruntfile.js
11
gruntfile.js
|
|
@ -23,11 +23,11 @@ module.exports = function (grunt) {
|
||||||
frameworks: ['jasmine', 'commonjs'],
|
frameworks: ['jasmine', 'commonjs'],
|
||||||
files: [
|
files: [
|
||||||
'src/**/*.js',
|
'src/**/*.js',
|
||||||
'test/unit/specs/*.js'
|
'test/unit/**/*.js'
|
||||||
],
|
],
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'src/**/*.js': ['commonjs'],
|
'src/**/*.js': ['commonjs'],
|
||||||
'test/unit/specs/*.js': ['commonjs']
|
'test/unit/**/*.js': ['commonjs']
|
||||||
},
|
},
|
||||||
singleRun: true
|
singleRun: true
|
||||||
},
|
},
|
||||||
|
|
@ -36,6 +36,12 @@ module.exports = function (grunt) {
|
||||||
browsers: ['Chrome', 'Firefox'],
|
browsers: ['Chrome', 'Firefox'],
|
||||||
reporters: ['progress']
|
reporters: ['progress']
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
phantom: {
|
||||||
|
options: {
|
||||||
|
browsers: ['PhantomJS'],
|
||||||
|
reporters: ['progress']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -72,6 +78,7 @@ module.exports = function (grunt) {
|
||||||
})
|
})
|
||||||
|
|
||||||
grunt.registerTask('unit', ['karma:browsers'])
|
grunt.registerTask('unit', ['karma:browsers'])
|
||||||
|
grunt.registerTask('phantom', ['karma:phantom'])
|
||||||
grunt.registerTask('watch', ['browserify:watch'])
|
grunt.registerTask('watch', ['browserify:watch'])
|
||||||
grunt.registerTask('build', ['browserify:build'])
|
grunt.registerTask('build', ['browserify:build'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
var _ = require('../util')
|
||||||
|
var slice = [].slice
|
||||||
|
var arrayAugmentations = Object.create(Array.prototype)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept mutating methods and emit events
|
||||||
|
*/
|
||||||
|
|
||||||
|
;[
|
||||||
|
'push',
|
||||||
|
'pop',
|
||||||
|
'shift',
|
||||||
|
'unshift',
|
||||||
|
'splice',
|
||||||
|
'sort',
|
||||||
|
'reverse'
|
||||||
|
]
|
||||||
|
.forEach(function (method) {
|
||||||
|
// cache original method
|
||||||
|
var original = Array.prototype[method]
|
||||||
|
// define wrapped method
|
||||||
|
_.define(arrayAugmentations, method, function () {
|
||||||
|
var args = slice.call(arguments)
|
||||||
|
var result = original.apply(this, args)
|
||||||
|
var ob = this.$observer
|
||||||
|
var inserted, removed
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'push':
|
||||||
|
case 'unshift':
|
||||||
|
inserted = args
|
||||||
|
break
|
||||||
|
case 'pop':
|
||||||
|
case 'shift':
|
||||||
|
removed = [result]
|
||||||
|
break
|
||||||
|
case 'splice':
|
||||||
|
inserted = args.slice(2)
|
||||||
|
removed = result
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ob.link(inserted)
|
||||||
|
ob.unlink(removed)
|
||||||
|
// empty key, value is self
|
||||||
|
ob.emit('mutate', '', this, {
|
||||||
|
method : method,
|
||||||
|
args : args,
|
||||||
|
result : result,
|
||||||
|
inserted : inserted,
|
||||||
|
removed : removed
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swap the element at the given index with a new value
|
||||||
|
* and emits corresponding event.
|
||||||
|
*
|
||||||
|
* @param {Number} index
|
||||||
|
* @param {*} val
|
||||||
|
* @return {*} - replaced element
|
||||||
|
*/
|
||||||
|
|
||||||
|
_.define(arrayAugmentations, '$set', function (index, val) {
|
||||||
|
if (index >= this.length) {
|
||||||
|
this.length = index + 1
|
||||||
|
}
|
||||||
|
return this.splice(index, 1, val)[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to remove the element at given index.
|
||||||
|
*
|
||||||
|
* @param {Number} index
|
||||||
|
* @param {*} val
|
||||||
|
*/
|
||||||
|
|
||||||
|
_.define(arrayAugmentations, '$remove', function (index) {
|
||||||
|
if (index > -1) {
|
||||||
|
return this.splice(index, 1)[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
var _ = require('../util')
|
||||||
|
var objectAgumentations = Object.create(Object.prototype)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new property to an observed object
|
||||||
|
* and emits corresponding event
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* @param {*} val
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
_.define(objectAgumentations, '$add', function (key, val) {
|
||||||
|
if (this.hasOwnProperty(key)) return
|
||||||
|
this[key] = val
|
||||||
|
this.$observer.convert(key, val)
|
||||||
|
this.$observer.emit('add', key, val)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a property from an observed object
|
||||||
|
* and emits corresponding event
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
_.define(objectAgumentations, '$delete', function (key) {
|
||||||
|
if (!this.hasOwnProperty(key)) return
|
||||||
|
// trigger set events
|
||||||
|
this[key] = undefined
|
||||||
|
delete this[key]
|
||||||
|
this.$observer.emit('delete', key)
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = objectAgumentations
|
||||||
|
|
@ -1,75 +1,174 @@
|
||||||
var _ = require('../util')
|
var _ = require('../util')
|
||||||
var Emitter = require('../emitter')
|
var Emitter = require('../emitter')
|
||||||
|
var arrayAugmentations = require('./array-augmentations')
|
||||||
|
var objectAugmentations = require('./object-augmentations')
|
||||||
|
|
||||||
|
// Type enums
|
||||||
|
|
||||||
|
var ARRAY = 0
|
||||||
|
var OBJECT = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observer class that are attached to each observed
|
* Observer class that are attached to each observed
|
||||||
* object. They are essentially event emitters, but can
|
* object. They are essentially event emitters, but can
|
||||||
* connect to each other and relay the events up the nested
|
* connect to each other like nodes to map the hierarchy
|
||||||
* object chain.
|
* of data objects. Once connected, detected change events
|
||||||
|
* can propagate up the nested object chain.
|
||||||
|
*
|
||||||
|
* The constructor can be invoked without arguments to
|
||||||
|
* create a value-less observer that simply listens to
|
||||||
|
* other observers.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @extends Emitter
|
* @extends Emitter
|
||||||
* @private
|
* @param {Array|Object} [value]
|
||||||
|
* @param {Number} [type]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Observer () {
|
function Observer (value, type) {
|
||||||
Emitter.call(this)
|
Emitter.call(this)
|
||||||
this.connections = Object.create(null)
|
this.value = value
|
||||||
|
this.type = type
|
||||||
|
this.initiated = false
|
||||||
|
this.children = Object.create(null)
|
||||||
|
if (value) {
|
||||||
|
_.define(value, '$observer', this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var p = Observer.prototype = Object.create(Emitter.prototype)
|
var p = Observer.prototype = Object.create(Emitter.prototype)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe an object of unkown type.
|
* Initialize the observation based on value type.
|
||||||
*
|
* Should only be called once.
|
||||||
* @param {*} obj
|
|
||||||
* @return {Boolean} - returns true if successfully observed.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
p.observe = function (obj) {
|
p.init = function () {
|
||||||
if (obj && obj.$observer) {
|
var value = this.value
|
||||||
// already observed
|
if (this.type === ARRAY) {
|
||||||
return
|
_.augment(value, arrayAugmentations)
|
||||||
|
this.link(value)
|
||||||
|
} else if (this.type === OBJECT) {
|
||||||
|
_.augment(value, objectAugmentations)
|
||||||
|
this.walk(value)
|
||||||
}
|
}
|
||||||
if (_.isArray(obj)) {
|
this.initiated = true
|
||||||
this.observeArray(obj)
|
}
|
||||||
return true
|
|
||||||
|
/**
|
||||||
|
* Walk through each property, converting them and adding them as child.
|
||||||
|
* This method should only be called when value type is Object.
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.walk = function (obj) {
|
||||||
|
var key, val, ob
|
||||||
|
for (key in obj) {
|
||||||
|
val = obj[key]
|
||||||
|
ob = Observer.create(val)
|
||||||
|
if (ob) {
|
||||||
|
this.add(key, ob)
|
||||||
|
if (ob.initiated) {
|
||||||
|
this.deliver(key, val)
|
||||||
|
} else {
|
||||||
|
ob.init()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.convert(key, val)
|
||||||
}
|
}
|
||||||
if (_.isObject(obj)) {
|
|
||||||
this.observeObject(obj)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to another Observer instance,
|
* Link a list of items to the observer's value Array.
|
||||||
|
* When any of these items emit change event, the Array will be notified.
|
||||||
|
*
|
||||||
|
* @param {Array} items
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.link = function (items) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink the items from the observer's value Array.
|
||||||
|
*
|
||||||
|
* @param {Array} items
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.unlink = function (items) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a tip value into getter/setter so we can emit the events
|
||||||
|
* when the property is accessed/changed.
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* @param {*} val
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.convert = function (key, val) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk through an already observed object and emit its tip values.
|
||||||
|
* This is necessary because newly observed objects emit their values
|
||||||
|
* during init; for already observed ones we can skip the initialization,
|
||||||
|
* but still need to emit the values.
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* @param {*} val
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.deliver = function (key, val) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a child observer for a property key,
|
||||||
* capture its get/set/mutate events and relay the events
|
* capture its get/set/mutate events and relay the events
|
||||||
* while prepending a key segment to the path.
|
* while prepending a key segment to the path.
|
||||||
*
|
*
|
||||||
* @param {Observer} target
|
|
||||||
* @param {String} key
|
* @param {String} key
|
||||||
|
* @param {Observer} ob
|
||||||
*/
|
*/
|
||||||
|
|
||||||
p.connect = function (target, key) {
|
p.add = function (key, ob) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from a connected target Observer.
|
* Remove a child observer.
|
||||||
*
|
*
|
||||||
* @param {Observer} target
|
|
||||||
* @param {String} key
|
* @param {String} key
|
||||||
|
* @param {Observer} ob
|
||||||
*/
|
*/
|
||||||
|
|
||||||
p.disconnect = function (target, key) {
|
p.remove = function (key, ob) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixin Array and Object observe methods
|
* Attempt to create an observer instance for a value,
|
||||||
|
* returns the new observer if successfully observed,
|
||||||
|
* or the existing observer if the value already has one.
|
||||||
|
*
|
||||||
|
* @param {*} value
|
||||||
|
* @return {Observer}
|
||||||
|
* @static
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_.mixin(p, require('./array'))
|
Observer.create = function (value) {
|
||||||
_.mixin(p, require('./object'))
|
if (value && value.$observer) {
|
||||||
|
return value.$observer
|
||||||
|
} if (_.isArray(value)) {
|
||||||
|
return new Observer(value, ARRAY)
|
||||||
|
} else if (_.isObject(value)) {
|
||||||
|
return new Observer(value, OBJECT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Observer
|
module.exports = Observer
|
||||||
26
src/util.js
26
src/util.js
|
|
@ -37,7 +37,7 @@ exports.isArray = function (obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a readonly, in-enumerable property
|
* Define a non-enumerable property
|
||||||
*
|
*
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} key
|
* @param {String} key
|
||||||
|
|
@ -48,7 +48,29 @@ exports.define = function (obj, key, val) {
|
||||||
Object.defineProperty(obj, key, {
|
Object.defineProperty(obj, key, {
|
||||||
value : val,
|
value : val,
|
||||||
enumerable : false,
|
enumerable : false,
|
||||||
writable: false,
|
writable : true,
|
||||||
configurable : true
|
configurable : true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Augment an target Object or Array by either
|
||||||
|
* intercepting the prototype chain using __proto__,
|
||||||
|
* or copy over property descriptors
|
||||||
|
*
|
||||||
|
* @param {Object|Array} target
|
||||||
|
* @param {Object} proto
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ('__proto__' in {}) {
|
||||||
|
exports.augment = function (target, proto) {
|
||||||
|
target.__proto__ = proto
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exports.augment = function (target, proto) {
|
||||||
|
Object.getOwnPropertyNames(proto).forEach(function (key) {
|
||||||
|
var descriptor = Object.getOwnPropertyDescriptor(proto, key)
|
||||||
|
Object.defineProperty(target, key, descriptor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
var Observer = require('../../src/observer/observer')
|
||||||
|
|
||||||
|
describe('Observer', function () {
|
||||||
|
|
||||||
|
it('should work', function () {
|
||||||
|
var obj = {}
|
||||||
|
var ob = Observer.create(obj)
|
||||||
|
ob.init()
|
||||||
|
expect(obj.$add).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
var Vue = require('../../../src/vue.js')
|
|
||||||
|
|
||||||
describe('test', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
expect(Vue).toBeDefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Loading…
Reference in New Issue