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, | ||||
|   "evil": true, | ||||
|   "eqnull": true, | ||||
|   "proto": true, | ||||
|   "globals": { | ||||
|     "console": true | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										11
									
								
								gruntfile.js
								
								
								
								
							
							
						
						
									
										11
									
								
								gruntfile.js
								
								
								
								
							|  | @ -23,11 +23,11 @@ module.exports = function (grunt) { | |||
|         frameworks: ['jasmine', 'commonjs'], | ||||
|         files: [ | ||||
|           'src/**/*.js', | ||||
|           'test/unit/specs/*.js' | ||||
|           'test/unit/**/*.js' | ||||
|         ], | ||||
|         preprocessors: { | ||||
|           'src/**/*.js': ['commonjs'], | ||||
|           'test/unit/specs/*.js': ['commonjs'] | ||||
|           'test/unit/**/*.js': ['commonjs'] | ||||
|         }, | ||||
|         singleRun: true | ||||
|       }, | ||||
|  | @ -36,6 +36,12 @@ module.exports = function (grunt) { | |||
|           browsers: ['Chrome', 'Firefox'], | ||||
|           reporters: ['progress'] | ||||
|         } | ||||
|       }, | ||||
|       phantom: { | ||||
|         options: { | ||||
|           browsers: ['PhantomJS'], | ||||
|           reporters: ['progress'] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|  | @ -72,6 +78,7 @@ module.exports = function (grunt) { | |||
|   }) | ||||
| 
 | ||||
|   grunt.registerTask('unit', ['karma:browsers']) | ||||
|   grunt.registerTask('phantom', ['karma:phantom']) | ||||
|   grunt.registerTask('watch', ['browserify:watch']) | ||||
|   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 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 | ||||
|  * object. They are essentially event emitters, but can | ||||
|  * connect to each other and relay the events up the nested | ||||
|  * object chain. | ||||
|  * connect to each other like nodes to map the hierarchy | ||||
|  * 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 | ||||
|  * @extends Emitter | ||||
|  * @private | ||||
|  * @param {Array|Object} [value] | ||||
|  * @param {Number} [type] | ||||
|  */ | ||||
| 
 | ||||
| function Observer () { | ||||
| function Observer (value, type) { | ||||
|   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) | ||||
| 
 | ||||
| /** | ||||
|  * Observe an object of unkown type. | ||||
|  * | ||||
|  * @param {*} obj | ||||
|  * @return {Boolean} - returns true if successfully observed. | ||||
|  * Initialize the observation based on value type. | ||||
|  * Should only be called once. | ||||
|  */ | ||||
| 
 | ||||
| p.observe = function (obj) { | ||||
|   if (obj && obj.$observer) { | ||||
|     // already observed
 | ||||
|     return | ||||
| p.init = function () { | ||||
|   var value = this.value | ||||
|   if (this.type === ARRAY) { | ||||
|     _.augment(value, arrayAugmentations) | ||||
|     this.link(value) | ||||
|   } else if (this.type === OBJECT) { | ||||
|     _.augment(value, objectAugmentations) | ||||
|     this.walk(value) | ||||
|   } | ||||
|   if (_.isArray(obj)) { | ||||
|     this.observeArray(obj) | ||||
|     return true | ||||
|   this.initiated = 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 | ||||
|  * while prepending a key segment to the path. | ||||
|  * | ||||
|  * @param {Observer} target | ||||
|  * @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 {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')) | ||||
| _.mixin(p, require('./object')) | ||||
| Observer.create = function (value) { | ||||
|   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 | ||||
							
								
								
									
										32
									
								
								src/util.js
								
								
								
								
							
							
						
						
									
										32
									
								
								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 {String} key | ||||
|  | @ -46,9 +46,31 @@ exports.isArray = function (obj) { | |||
| 
 | ||||
| exports.define = function (obj, key, val) { | ||||
|   Object.defineProperty(obj, key, { | ||||
|     value: val, | ||||
|     enumerable: false, | ||||
|     writable: false, | ||||
|     configurable: true | ||||
|     value        : val, | ||||
|     enumerable   : false, | ||||
|     writable     : 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