diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/core/observer | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-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.js | 45 | ||||
| -rw-r--r-- | node_modules/vue/src/core/observer/dep.js | 58 | ||||
| -rw-r--r-- | node_modules/vue/src/core/observer/index.js | 273 | ||||
| -rw-r--r-- | node_modules/vue/src/core/observer/scheduler.js | 148 | ||||
| -rw-r--r-- | node_modules/vue/src/core/observer/traverse.js | 40 | ||||
| -rw-r--r-- | node_modules/vue/src/core/observer/watcher.js | 240 |
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 + } + } +} |
