Last active
September 3, 2019 19:29
-
-
Save WJWang/0e62ae12453d29f3533a2e49808e9465 to your computer and use it in GitHub Desktop.
Vue 2.6.10 Study (Reactivity)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
const setter = property && property.set | |
if ((!getter || setter) && arguments.length === 2) { | |
val = obj[key] | |
} | |
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() | |
} | |
// #7981: for accessor properties without setter | |
if (getter && !setter) return | |
if (setter) { | |
setter.call(obj, newVal) | |
} else { | |
val = newVal | |
} | |
childOb = !shallow && observe(newVal) | |
dep.notify() | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class Observer { | |
value: any; | |
dep: Dep; | |
vmCount: number; // number of vms that have 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)) { | |
if (hasProto) { | |
protoAugment(value, arrayMethods) | |
} else { | |
copyAugment(value, arrayMethods, arrayKeys) | |
} | |
this.observeArray(value) | |
} else { | |
this.walk(value) | |
} | |
} | |
/** | |
* Walk through all properties 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]) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @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 have 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)) { | |
if (hasProto) { | |
protoAugment(value, arrayMethods) | |
} else { | |
copyAugment(value, arrayMethods, arrayKeys) | |
} | |
this.observeArray(value) | |
} else { | |
this.walk(value) | |
} | |
} | |
/** | |
* Walk through all properties 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 a target Object or Array by intercepting | |
* the prototype chain using __proto__ | |
*/ | |
function protoAugment (target, src: Object) { | |
/* eslint-disable no-proto */ | |
target.__proto__ = src | |
/* eslint-enable no-proto */ | |
} | |
/** | |
* Augment a 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 | |
const setter = property && property.set | |
if ((!getter || setter) && arguments.length === 2) { | |
val = obj[key] | |
} | |
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() | |
} | |
// #7981: for accessor properties without setter | |
if (getter && !setter) return | |
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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment