var _ = require('../util') var config = require('../config') var Dep = require('./dep') var arrayMethods = require('./array') var arrayKeys = Object.getOwnPropertyNames(arrayMethods) require('./object') var uid = 0 /** * Type enums */ var ARRAY = 0 var OBJECT = 1 /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ * * @param {Object|Array} target * @param {Object} proto */ function protoAugment (target, src) { target.__proto__ = src } /** * Augment an target Object or Array by defining * hidden properties. * * @param {Object|Array} target * @param {Object} proto */ function copyAugment (target, src, keys) { var i = keys.length var key while (i--) { key = keys[i] _.define(target, key, src[key]) } } /** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. * * @param {Array|Object} value * @param {Number} type * @constructor */ function Observer (value, type) { this.id = ++uid this.value = value this.active = true this.deps = [] _.define(value, '__ob__', this) if (type === ARRAY) { var augment = config.proto && _.hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else if (type === OBJECT) { this.walk(value) } } Observer.target = null var p = Observer.prototype /** * 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|undefined} * @static */ Observer.create = function (value) { if ( value && value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer ) { return value.__ob__ } else if (_.isArray(value)) { return new Observer(value, ARRAY) } else if ( _.isPlainObject(value) && !value._isVue // avoid Vue instance ) { return new Observer(value, OBJECT) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. Properties prefixed with `$` or `_` * and accessor properties are ignored. * * @param {Object} obj */ p.walk = function (obj) { var keys = Object.keys(obj) var i = keys.length var key, prefix while (i--) { key = keys[i] prefix = key.charCodeAt(0) if (prefix !== 0x24 && prefix !== 0x5F) { // skip $ or _ this.convert(key, obj[key]) } } } /** * Try to carete an observer for a child value, * and if value is array, link dep to the array. * * @param {*} val * @return {Dep|undefined} */ p.observe = function (val) { return Observer.create(val) } /** * Observe a list of Array items. * * @param {Array} items */ p.observeArray = function (items) { var i = items.length while (i--) { this.observe(items[i]) } } /** * Convert a property 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) { var ob = this var childOb = ob.observe(val) var dep = new Dep() if (childOb) { childOb.deps.push(dep) } Object.defineProperty(ob.value, key, { enumerable: true, configurable: true, get: function () { // Observer.target is a watcher whose getter is // currently being evaluated. if (ob.active && Observer.target) { Observer.target.addDep(dep) } return val }, set: function (newVal) { if (newVal === val) return // remove dep from old value var oldChildOb = val && val.__ob__ if (oldChildOb) { var oldDeps = oldChildOb.deps oldDeps.splice(oldDeps.indexOf(dep), 1) } val = newVal // add dep to new value var newChildOb = ob.observe(newVal) if (newChildOb) { newChildOb.deps.push(dep) } dep.notify() } }) } /** * Notify change on all self deps on an observer. * This is called when a mutable value mutates. e.g. * when an Array's mutating methods are called, or an * Object's $add/$delete are called. */ p.notify = function () { var deps = this.deps for (var i = 0, l = deps.length; i < l; i++) { deps[i].notify() } } /** * Add an owner vm, so that when $add/$delete mutations * happen we can notify owner vms to proxy the keys and * digest the watchers. This is only called when the object * is observed as an instance's root $data. * * @param {Vue} vm */ p.addVm = function (vm) { (this.vms = this.vms || []).push(vm) } /** * Remove an owner vm. This is called when the object is * swapped out as an instance's $data object. * * @param {Vue} vm */ p.removeVm = function (vm) { this.vms.splice(this.vms.indexOf(vm), 1) } module.exports = Observer