silk-gui
Version:
GUI for developers and Node OS
236 lines (207 loc) • 4.99 kB
JavaScript
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