| /* eslint-disable */ |
| |
| |
| import Dep from './dep' |
| import { arrayMethods } from './array' |
| import { |
| def, |
| remove, |
| isObject, |
| isPlainObject, |
| hasProto, |
| hasOwn, |
| isReserved |
| } from '../util/index' |
| |
| const arrayKeys = Object.getOwnPropertyNames(arrayMethods) |
| |
| /** |
| * 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 |
| * @constructor |
| */ |
| |
| export function Observer (value) { |
| this.value = value |
| this.dep = new Dep() |
| def(value, '__ob__', this) |
| if (Array.isArray(value)) { |
| const augment = hasProto |
| ? protoAugment |
| : copyAugment |
| augment(value, arrayMethods, arrayKeys) |
| this.observeArray(value) |
| } else { |
| this.walk(value) |
| } |
| } |
| |
| // Instance methods |
| |
| /** |
| * Walk through each property and convert them into |
| * getter/setters. This method should only be called when |
| * value type is Object. |
| * |
| * @param {Object} obj |
| */ |
| |
| Observer.prototype.walk = function (obj) { |
| for (let key in obj) { |
| this.convert(key, obj[key]) |
| } |
| } |
| |
| /** |
| * Observe a list of Array items. |
| * |
| * @param {Array} items |
| */ |
| |
| Observer.prototype.observeArray = function (items) { |
| for (let i = 0, l = items.length; i < l; i++) { |
| 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 |
| */ |
| |
| Observer.prototype.convert = function (key, val) { |
| defineReactive(this.value, key, val) |
| } |
| |
| /** |
| * Add an owner vm, so that when $set/$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 |
| */ |
| |
| Observer.prototype.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 |
| */ |
| |
| /* istanbul ignore next */ |
| Observer.prototype.removeVm = function (vm) { |
| remove(this.vms, vm) |
| } |
| |
| // helpers |
| |
| /** |
| * Augment an target Object or Array by intercepting |
| * the prototype chain using __proto__ |
| * |
| * @param {Object|Array} target |
| * @param {Object} src |
| */ |
| |
| function protoAugment (target, src) { |
| /* eslint-disable no-proto */ |
| target.__proto__ = src |
| /* eslint-enable no-proto */ |
| } |
| |
| /** |
| * Augment an target Object or Array by defining |
| * hidden properties. |
| * |
| * @param {Object|Array} target |
| * @param {Object} proto |
| */ |
| |
| /* istanbul ignore next */ |
| function copyAugment (target, src, keys) { |
| for (let i = 0, l = keys.length; i < l; i++) { |
| const key = keys[i] |
| def(target, key, src[key]) |
| } |
| } |
| |
| /** |
| * 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 |
| * @param {Vue} [vm] |
| * @return {Observer|undefined} |
| * @static |
| */ |
| |
| export function observe (value, vm) { |
| if (!isObject(value)) { |
| return |
| } |
| let ob |
| if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { |
| ob = value.__ob__ |
| } else if ( |
| (Array.isArray(value) || isPlainObject(value)) && |
| Object.isExtensible(value) && |
| !value._isVue |
| ) { |
| ob = new Observer(value) |
| } |
| if (ob && vm) { |
| ob.addVm(vm) |
| } |
| return ob |
| } |
| |
| /** |
| * Define a reactive property on an Object. |
| * |
| * @param {Object} obj |
| * @param {String} key |
| * @param {*} val |
| */ |
| |
| export function defineReactive (obj, key, val) { |
| const dep = new Dep() |
| |
| const property = Object.getOwnPropertyDescriptor(obj, key) |
| if (property && property.configurable === false) { |
| return |
| } |
| |
| // cater for pre-defined getter/setters |
| const getter = property && property.get |
| const setter = property && property.set |
| |
| let childOb = observe(val) |
| Object.defineProperty(obj, key, { |
| enumerable: true, |
| configurable: true, |
| get: function reactiveGetter () { |
| const value = getter ? getter.call(obj) : val |
| if (Dep.target) { |
| dep.depend() |
| if (childOb) { |
| childOb.dep.depend() |
| } |
| if (Array.isArray(value)) { |
| for (let e, i = 0, l = value.length; i < l; i++) { |
| e = value[i] |
| e && e.__ob__ && e.__ob__.dep.depend() |
| } |
| } |
| } |
| return value |
| }, |
| set: function reactiveSetter (newVal) { |
| const value = getter ? getter.call(obj) : val |
| if (newVal === value) { |
| return |
| } |
| if (setter) { |
| setter.call(obj, newVal) |
| } else { |
| val = newVal |
| } |
| childOb = observe(newVal) |
| dep.notify() |
| } |
| }) |
| } |
| |
| /** |
| * Set a property on an object. Adds the new property and |
| * triggers change notification if the property doesn't |
| * already exist. |
| * |
| * @param {Object} obj |
| * @param {String} key |
| * @param {*} val |
| * @public |
| */ |
| |
| /* istanbul ignore next */ |
| export function set (obj, key, val) { |
| if (Array.isArray(obj)) { |
| return obj.splice(key, 1, val) |
| } |
| if (hasOwn(obj, key)) { |
| obj[key] = val |
| return |
| } |
| if (obj._isVue) { |
| set(obj._data, key, val) |
| return |
| } |
| const ob = obj.__ob__ |
| if (!ob) { |
| obj[key] = val |
| return |
| } |
| ob.convert(key, val) |
| ob.dep.notify() |
| if (ob.vms) { |
| let i = ob.vms.length |
| while (i--) { |
| const vm = ob.vms[i] |
| proxy(vm, key) |
| // vm.$forceUpdate() |
| } |
| } |
| return val |
| } |
| |
| /** |
| * Delete a property and trigger change if necessary. |
| * |
| * @param {Object} obj |
| * @param {String} key |
| */ |
| |
| /* istanbul ignore next */ |
| export function del (obj, key) { |
| if (!hasOwn(obj, key)) { |
| return |
| } |
| delete obj[key] |
| const ob = obj.__ob__ |
| |
| if (!ob) { |
| if (obj._isVue) { |
| delete obj._data[key] |
| // obj.$forceUpdate() |
| } |
| return |
| } |
| ob.dep.notify() |
| if (ob.vms) { |
| let i = ob.vms.length |
| while (i--) { |
| const vm = ob.vms[i] |
| unproxy(vm, key) |
| // vm.$forceUpdate() |
| } |
| } |
| } |
| |
| const KEY_WORDS = ['$index', '$value', '$event'] |
| export function proxy (vm, key) { |
| if (KEY_WORDS.indexOf(key) > -1 || !isReserved(key)) { |
| Object.defineProperty(vm, key, { |
| configurable: true, |
| enumerable: true, |
| get: function proxyGetter () { |
| return vm._data[key] |
| }, |
| set: function proxySetter (val) { |
| vm._data[key] = val |
| } |
| }) |
| } |
| } |
| |
| /* istanbul ignore next */ |
| export function unproxy (vm, key) { |
| if (!isReserved(key)) { |
| delete vm[key] |
| } |
| } |