blob: 103dd0d23b7353df4dcae540346762f35c3977e4 [file] [log] [blame]
/* 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]
}
}