aboutsummaryrefslogtreecommitdiff
path: root/node_modules/vue/src/core/observer
diff options
context:
space:
mode:
authorruki <waruqi@gmail.com>2018-11-08 00:38:48 +0800
committerruki <waruqi@gmail.com>2018-11-07 21:53:09 +0800
commit26105034da4fcce7ac883c899d781f016559310d (patch)
treec459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/core/observer
parent2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff)
downloadxmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz
xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/core/observer')
-rw-r--r--node_modules/vue/src/core/observer/array.js45
-rw-r--r--node_modules/vue/src/core/observer/dep.js58
-rw-r--r--node_modules/vue/src/core/observer/index.js273
-rw-r--r--node_modules/vue/src/core/observer/scheduler.js148
-rw-r--r--node_modules/vue/src/core/observer/traverse.js40
-rw-r--r--node_modules/vue/src/core/observer/watcher.js240
6 files changed, 804 insertions, 0 deletions
diff --git a/node_modules/vue/src/core/observer/array.js b/node_modules/vue/src/core/observer/array.js
new file mode 100644
index 00000000..684e37ec
--- /dev/null
+++ b/node_modules/vue/src/core/observer/array.js
@@ -0,0 +1,45 @@
+/*
+ * not type checking this file because flow doesn't play well with
+ * dynamically accessing methods on Array prototype
+ */
+
+import { def } from '../util/index'
+
+const arrayProto = Array.prototype
+export const arrayMethods = Object.create(arrayProto)
+
+const methodsToPatch = [
+ 'push',
+ 'pop',
+ 'shift',
+ 'unshift',
+ 'splice',
+ 'sort',
+ 'reverse'
+]
+
+/**
+ * Intercept mutating methods and emit events
+ */
+methodsToPatch.forEach(function (method) {
+ // cache original method
+ const original = arrayProto[method]
+ def(arrayMethods, method, function mutator (...args) {
+ const result = original.apply(this, args)
+ const ob = this.__ob__
+ let inserted
+ switch (method) {
+ case 'push':
+ case 'unshift':
+ inserted = args
+ break
+ case 'splice':
+ inserted = args.slice(2)
+ break
+ }
+ if (inserted) ob.observeArray(inserted)
+ // notify change
+ ob.dep.notify()
+ return result
+ })
+})
diff --git a/node_modules/vue/src/core/observer/dep.js b/node_modules/vue/src/core/observer/dep.js
new file mode 100644
index 00000000..abf3b275
--- /dev/null
+++ b/node_modules/vue/src/core/observer/dep.js
@@ -0,0 +1,58 @@
+/* @flow */
+
+import type Watcher from './watcher'
+import { remove } from '../util/index'
+
+let uid = 0
+
+/**
+ * A dep is an observable that can have multiple
+ * directives subscribing to it.
+ */
+export default class Dep {
+ static target: ?Watcher;
+ id: number;
+ subs: Array<Watcher>;
+
+ constructor () {
+ this.id = uid++
+ this.subs = []
+ }
+
+ addSub (sub: Watcher) {
+ this.subs.push(sub)
+ }
+
+ removeSub (sub: Watcher) {
+ remove(this.subs, sub)
+ }
+
+ depend () {
+ if (Dep.target) {
+ Dep.target.addDep(this)
+ }
+ }
+
+ notify () {
+ // stabilize the subscriber list first
+ const subs = this.subs.slice()
+ for (let i = 0, l = subs.length; i < l; i++) {
+ subs[i].update()
+ }
+ }
+}
+
+// the current target watcher being evaluated.
+// this is globally unique because there could be only one
+// watcher being evaluated at any time.
+Dep.target = null
+const targetStack = []
+
+export function pushTarget (_target: ?Watcher) {
+ if (Dep.target) targetStack.push(Dep.target)
+ Dep.target = _target
+}
+
+export function popTarget () {
+ Dep.target = targetStack.pop()
+}
diff --git a/node_modules/vue/src/core/observer/index.js b/node_modules/vue/src/core/observer/index.js
new file mode 100644
index 00000000..4a030ed7
--- /dev/null
+++ b/node_modules/vue/src/core/observer/index.js
@@ -0,0 +1,273 @@
+/* @flow */
+
+import Dep from './dep'
+import VNode from '../vdom/vnode'
+import { arrayMethods } from './array'
+import {
+ def,
+ warn,
+ hasOwn,
+ hasProto,
+ isObject,
+ isPlainObject,
+ isPrimitive,
+ isUndef,
+ isValidArrayIndex,
+ isServerRendering
+} from '../util/index'
+
+const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
+
+/**
+ * In some cases we may want to disable observation inside a component's
+ * update computation.
+ */
+export let shouldObserve: boolean = true
+
+export function toggleObserving (value: boolean) {
+ shouldObserve = value
+}
+
+/**
+ * Observer class that is attached to each observed
+ * object. Once attached, the observer converts the target
+ * object's property keys into getter/setters that
+ * collect dependencies and dispatch updates.
+ */
+export class Observer {
+ value: any;
+ dep: Dep;
+ vmCount: number; // number of vms that has this object as root $data
+
+ constructor (value: any) {
+ this.value = value
+ this.dep = new Dep()
+ this.vmCount = 0
+ def(value, '__ob__', this)
+ if (Array.isArray(value)) {
+ const augment = hasProto
+ ? protoAugment
+ : copyAugment
+ augment(value, arrayMethods, arrayKeys)
+ this.observeArray(value)
+ } else {
+ this.walk(value)
+ }
+ }
+
+ /**
+ * Walk through each property and convert them into
+ * getter/setters. This method should only be called when
+ * value type is Object.
+ */
+ walk (obj: Object) {
+ const keys = Object.keys(obj)
+ for (let i = 0; i < keys.length; i++) {
+ defineReactive(obj, keys[i])
+ }
+ }
+
+ /**
+ * Observe a list of Array items.
+ */
+ observeArray (items: Array<any>) {
+ for (let i = 0, l = items.length; i < l; i++) {
+ observe(items[i])
+ }
+ }
+}
+
+// helpers
+
+/**
+ * Augment an target Object or Array by intercepting
+ * the prototype chain using __proto__
+ */
+function protoAugment (target, src: Object, keys: any) {
+ /* eslint-disable no-proto */
+ target.__proto__ = src
+ /* eslint-enable no-proto */
+}
+
+/**
+ * Augment an target Object or Array by defining
+ * hidden properties.
+ */
+/* istanbul ignore next */
+function copyAugment (target: Object, src: Object, keys: Array<string>) {
+ 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.
+ */
+export function observe (value: any, asRootData: ?boolean): Observer | void {
+ if (!isObject(value) || value instanceof VNode) {
+ return
+ }
+ let ob: Observer | void
+ if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
+ ob = value.__ob__
+ } else if (
+ shouldObserve &&
+ !isServerRendering() &&
+ (Array.isArray(value) || isPlainObject(value)) &&
+ Object.isExtensible(value) &&
+ !value._isVue
+ ) {
+ ob = new Observer(value)
+ }
+ if (asRootData && ob) {
+ ob.vmCount++
+ }
+ return ob
+}
+
+/**
+ * Define a reactive property on an Object.
+ */
+export function defineReactive (
+ obj: Object,
+ key: string,
+ val: any,
+ customSetter?: ?Function,
+ shallow?: boolean
+) {
+ 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
+ if (!getter && arguments.length === 2) {
+ val = obj[key]
+ }
+ const setter = property && property.set
+
+ let childOb = !shallow && 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)) {
+ dependArray(value)
+ }
+ }
+ }
+ return value
+ },
+ set: function reactiveSetter (newVal) {
+ const value = getter ? getter.call(obj) : val
+ /* eslint-disable no-self-compare */
+ if (newVal === value || (newVal !== newVal && value !== value)) {
+ return
+ }
+ /* eslint-enable no-self-compare */
+ if (process.env.NODE_ENV !== 'production' && customSetter) {
+ customSetter()
+ }
+ if (setter) {
+ setter.call(obj, newVal)
+ } else {
+ val = newVal
+ }
+ childOb = !shallow && 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.
+ */
+export function set (target: Array<any> | Object, key: any, val: any): any {
+ if (process.env.NODE_ENV !== 'production' &&
+ (isUndef(target) || isPrimitive(target))
+ ) {
+ warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
+ }
+ if (Array.isArray(target) && isValidArrayIndex(key)) {
+ target.length = Math.max(target.length, key)
+ target.splice(key, 1, val)
+ return val
+ }
+ if (key in target && !(key in Object.prototype)) {
+ target[key] = val
+ return val
+ }
+ const ob = (target: any).__ob__
+ if (target._isVue || (ob && ob.vmCount)) {
+ process.env.NODE_ENV !== 'production' && warn(
+ 'Avoid adding reactive properties to a Vue instance or its root $data ' +
+ 'at runtime - declare it upfront in the data option.'
+ )
+ return val
+ }
+ if (!ob) {
+ target[key] = val
+ return val
+ }
+ defineReactive(ob.value, key, val)
+ ob.dep.notify()
+ return val
+}
+
+/**
+ * Delete a property and trigger change if necessary.
+ */
+export function del (target: Array<any> | Object, key: any) {
+ if (process.env.NODE_ENV !== 'production' &&
+ (isUndef(target) || isPrimitive(target))
+ ) {
+ warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
+ }
+ if (Array.isArray(target) && isValidArrayIndex(key)) {
+ target.splice(key, 1)
+ return
+ }
+ const ob = (target: any).__ob__
+ if (target._isVue || (ob && ob.vmCount)) {
+ process.env.NODE_ENV !== 'production' && warn(
+ 'Avoid deleting properties on a Vue instance or its root $data ' +
+ '- just set it to null.'
+ )
+ return
+ }
+ if (!hasOwn(target, key)) {
+ return
+ }
+ delete target[key]
+ if (!ob) {
+ return
+ }
+ ob.dep.notify()
+}
+
+/**
+ * Collect dependencies on array elements when the array is touched, since
+ * we cannot intercept array element access like property getters.
+ */
+function dependArray (value: Array<any>) {
+ for (let e, i = 0, l = value.length; i < l; i++) {
+ e = value[i]
+ e && e.__ob__ && e.__ob__.dep.depend()
+ if (Array.isArray(e)) {
+ dependArray(e)
+ }
+ }
+}
diff --git a/node_modules/vue/src/core/observer/scheduler.js b/node_modules/vue/src/core/observer/scheduler.js
new file mode 100644
index 00000000..fce86e5f
--- /dev/null
+++ b/node_modules/vue/src/core/observer/scheduler.js
@@ -0,0 +1,148 @@
+/* @flow */
+
+import type Watcher from './watcher'
+import config from '../config'
+import { callHook, activateChildComponent } from '../instance/lifecycle'
+
+import {
+ warn,
+ nextTick,
+ devtools
+} from '../util/index'
+
+export const MAX_UPDATE_COUNT = 100
+
+const queue: Array<Watcher> = []
+const activatedChildren: Array<Component> = []
+let has: { [key: number]: ?true } = {}
+let circular: { [key: number]: number } = {}
+let waiting = false
+let flushing = false
+let index = 0
+
+/**
+ * Reset the scheduler's state.
+ */
+function resetSchedulerState () {
+ index = queue.length = activatedChildren.length = 0
+ has = {}
+ if (process.env.NODE_ENV !== 'production') {
+ circular = {}
+ }
+ waiting = flushing = false
+}
+
+/**
+ * Flush both queues and run the watchers.
+ */
+function flushSchedulerQueue () {
+ flushing = true
+ let watcher, id
+
+ // Sort queue before flush.
+ // This ensures that:
+ // 1. Components are updated from parent to child. (because parent is always
+ // created before the child)
+ // 2. A component's user watchers are run before its render watcher (because
+ // user watchers are created before the render watcher)
+ // 3. If a component is destroyed during a parent component's watcher run,
+ // its watchers can be skipped.
+ queue.sort((a, b) => a.id - b.id)
+
+ // do not cache length because more watchers might be pushed
+ // as we run existing watchers
+ for (index = 0; index < queue.length; index++) {
+ watcher = queue[index]
+ id = watcher.id
+ has[id] = null
+ watcher.run()
+ // in dev build, check and stop circular updates.
+ if (process.env.NODE_ENV !== 'production' && has[id] != null) {
+ circular[id] = (circular[id] || 0) + 1
+ if (circular[id] > MAX_UPDATE_COUNT) {
+ warn(
+ 'You may have an infinite update loop ' + (
+ watcher.user
+ ? `in watcher with expression "${watcher.expression}"`
+ : `in a component render function.`
+ ),
+ watcher.vm
+ )
+ break
+ }
+ }
+ }
+
+ // keep copies of post queues before resetting state
+ const activatedQueue = activatedChildren.slice()
+ const updatedQueue = queue.slice()
+
+ resetSchedulerState()
+
+ // call component updated and activated hooks
+ callActivatedHooks(activatedQueue)
+ callUpdatedHooks(updatedQueue)
+
+ // devtool hook
+ /* istanbul ignore if */
+ if (devtools && config.devtools) {
+ devtools.emit('flush')
+ }
+}
+
+function callUpdatedHooks (queue) {
+ let i = queue.length
+ while (i--) {
+ const watcher = queue[i]
+ const vm = watcher.vm
+ if (vm._watcher === watcher && vm._isMounted) {
+ callHook(vm, 'updated')
+ }
+ }
+}
+
+/**
+ * Queue a kept-alive component that was activated during patch.
+ * The queue will be processed after the entire tree has been patched.
+ */
+export function queueActivatedComponent (vm: Component) {
+ // setting _inactive to false here so that a render function can
+ // rely on checking whether it's in an inactive tree (e.g. router-view)
+ vm._inactive = false
+ activatedChildren.push(vm)
+}
+
+function callActivatedHooks (queue) {
+ for (let i = 0; i < queue.length; i++) {
+ queue[i]._inactive = true
+ activateChildComponent(queue[i], true /* true */)
+ }
+}
+
+/**
+ * Push a watcher into the watcher queue.
+ * Jobs with duplicate IDs will be skipped unless it's
+ * pushed when the queue is being flushed.
+ */
+export function queueWatcher (watcher: Watcher) {
+ const id = watcher.id
+ if (has[id] == null) {
+ has[id] = true
+ if (!flushing) {
+ queue.push(watcher)
+ } else {
+ // if already flushing, splice the watcher based on its id
+ // if already past its id, it will be run next immediately.
+ let i = queue.length - 1
+ while (i > index && queue[i].id > watcher.id) {
+ i--
+ }
+ queue.splice(i + 1, 0, watcher)
+ }
+ // queue the flush
+ if (!waiting) {
+ waiting = true
+ nextTick(flushSchedulerQueue)
+ }
+ }
+}
diff --git a/node_modules/vue/src/core/observer/traverse.js b/node_modules/vue/src/core/observer/traverse.js
new file mode 100644
index 00000000..6b26524a
--- /dev/null
+++ b/node_modules/vue/src/core/observer/traverse.js
@@ -0,0 +1,40 @@
+/* @flow */
+
+import { _Set as Set, isObject } from '../util/index'
+import type { SimpleSet } from '../util/index'
+import VNode from '../vdom/vnode'
+
+const seenObjects = new Set()
+
+/**
+ * Recursively traverse an object to evoke all converted
+ * getters, so that every nested property inside the object
+ * is collected as a "deep" dependency.
+ */
+export function traverse (val: any) {
+ _traverse(val, seenObjects)
+ seenObjects.clear()
+}
+
+function _traverse (val: any, seen: SimpleSet) {
+ let i, keys
+ const isA = Array.isArray(val)
+ if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
+ return
+ }
+ if (val.__ob__) {
+ const depId = val.__ob__.dep.id
+ if (seen.has(depId)) {
+ return
+ }
+ seen.add(depId)
+ }
+ if (isA) {
+ i = val.length
+ while (i--) _traverse(val[i], seen)
+ } else {
+ keys = Object.keys(val)
+ i = keys.length
+ while (i--) _traverse(val[keys[i]], seen)
+ }
+}
diff --git a/node_modules/vue/src/core/observer/watcher.js b/node_modules/vue/src/core/observer/watcher.js
new file mode 100644
index 00000000..48a2e612
--- /dev/null
+++ b/node_modules/vue/src/core/observer/watcher.js
@@ -0,0 +1,240 @@
+/* @flow */
+
+import {
+ warn,
+ remove,
+ isObject,
+ parsePath,
+ _Set as Set,
+ handleError
+} from '../util/index'
+
+import { traverse } from './traverse'
+import { queueWatcher } from './scheduler'
+import Dep, { pushTarget, popTarget } from './dep'
+
+import type { SimpleSet } from '../util/index'
+
+let uid = 0
+
+/**
+ * A watcher parses an expression, collects dependencies,
+ * and fires callback when the expression value changes.
+ * This is used for both the $watch() api and directives.
+ */
+export default class Watcher {
+ vm: Component;
+ expression: string;
+ cb: Function;
+ id: number;
+ deep: boolean;
+ user: boolean;
+ lazy: boolean;
+ sync: boolean;
+ dirty: boolean;
+ active: boolean;
+ deps: Array<Dep>;
+ newDeps: Array<Dep>;
+ depIds: SimpleSet;
+ newDepIds: SimpleSet;
+ getter: Function;
+ value: any;
+
+ constructor (
+ vm: Component,
+ expOrFn: string | Function,
+ cb: Function,
+ options?: ?Object,
+ isRenderWatcher?: boolean
+ ) {
+ this.vm = vm
+ if (isRenderWatcher) {
+ vm._watcher = this
+ }
+ vm._watchers.push(this)
+ // options
+ if (options) {
+ this.deep = !!options.deep
+ this.user = !!options.user
+ this.lazy = !!options.lazy
+ this.sync = !!options.sync
+ } else {
+ this.deep = this.user = this.lazy = this.sync = false
+ }
+ this.cb = cb
+ this.id = ++uid // uid for batching
+ this.active = true
+ this.dirty = this.lazy // for lazy watchers
+ this.deps = []
+ this.newDeps = []
+ this.depIds = new Set()
+ this.newDepIds = new Set()
+ this.expression = process.env.NODE_ENV !== 'production'
+ ? expOrFn.toString()
+ : ''
+ // parse expression for getter
+ if (typeof expOrFn === 'function') {
+ this.getter = expOrFn
+ } else {
+ this.getter = parsePath(expOrFn)
+ if (!this.getter) {
+ this.getter = function () {}
+ process.env.NODE_ENV !== 'production' && warn(
+ `Failed watching path: "${expOrFn}" ` +
+ 'Watcher only accepts simple dot-delimited paths. ' +
+ 'For full control, use a function instead.',
+ vm
+ )
+ }
+ }
+ this.value = this.lazy
+ ? undefined
+ : this.get()
+ }
+
+ /**
+ * Evaluate the getter, and re-collect dependencies.
+ */
+ get () {
+ pushTarget(this)
+ let value
+ const vm = this.vm
+ try {
+ value = this.getter.call(vm, vm)
+ } catch (e) {
+ if (this.user) {
+ handleError(e, vm, `getter for watcher "${this.expression}"`)
+ } else {
+ throw e
+ }
+ } finally {
+ // "touch" every property so they are all tracked as
+ // dependencies for deep watching
+ if (this.deep) {
+ traverse(value)
+ }
+ popTarget()
+ this.cleanupDeps()
+ }
+ return value
+ }
+
+ /**
+ * Add a dependency to this directive.
+ */
+ addDep (dep: Dep) {
+ const id = dep.id
+ if (!this.newDepIds.has(id)) {
+ this.newDepIds.add(id)
+ this.newDeps.push(dep)
+ if (!this.depIds.has(id)) {
+ dep.addSub(this)
+ }
+ }
+ }
+
+ /**
+ * Clean up for dependency collection.
+ */
+ cleanupDeps () {
+ let i = this.deps.length
+ while (i--) {
+ const dep = this.deps[i]
+ if (!this.newDepIds.has(dep.id)) {
+ dep.removeSub(this)
+ }
+ }
+ let tmp = this.depIds
+ this.depIds = this.newDepIds
+ this.newDepIds = tmp
+ this.newDepIds.clear()
+ tmp = this.deps
+ this.deps = this.newDeps
+ this.newDeps = tmp
+ this.newDeps.length = 0
+ }
+
+ /**
+ * Subscriber interface.
+ * Will be called when a dependency changes.
+ */
+ update () {
+ /* istanbul ignore else */
+ if (this.lazy) {
+ this.dirty = true
+ } else if (this.sync) {
+ this.run()
+ } else {
+ queueWatcher(this)
+ }
+ }
+
+ /**
+ * Scheduler job interface.
+ * Will be called by the scheduler.
+ */
+ run () {
+ if (this.active) {
+ const value = this.get()
+ if (
+ value !== this.value ||
+ // Deep watchers and watchers on Object/Arrays should fire even
+ // when the value is the same, because the value may
+ // have mutated.
+ isObject(value) ||
+ this.deep
+ ) {
+ // set new value
+ const oldValue = this.value
+ this.value = value
+ if (this.user) {
+ try {
+ this.cb.call(this.vm, value, oldValue)
+ } catch (e) {
+ handleError(e, this.vm, `callback for watcher "${this.expression}"`)
+ }
+ } else {
+ this.cb.call(this.vm, value, oldValue)
+ }
+ }
+ }
+ }
+
+ /**
+ * Evaluate the value of the watcher.
+ * This only gets called for lazy watchers.
+ */
+ evaluate () {
+ this.value = this.get()
+ this.dirty = false
+ }
+
+ /**
+ * Depend on all deps collected by this watcher.
+ */
+ depend () {
+ let i = this.deps.length
+ while (i--) {
+ this.deps[i].depend()
+ }
+ }
+
+ /**
+ * Remove self from all dependencies' subscriber list.
+ */
+ teardown () {
+ if (this.active) {
+ // remove self from vm's watcher list
+ // this is a somewhat expensive operation so we skip it
+ // if the vm is being destroyed.
+ if (!this.vm._isBeingDestroyed) {
+ remove(this.vm._watchers, this)
+ }
+ let i = this.deps.length
+ while (i--) {
+ this.deps[i].removeSub(this)
+ }
+ this.active = false
+ }
+ }
+}