The inner function _init executes the lifecycle hook function beforeCreate before creating executes the following functions:
initInjections(vm)
initState(vm)
initProvide(vm)
Copy the code
Because initProvide(VM) and initProvide(VM) are the initInjections added to Vue in later versions, and it involves reactive data processing functions, while initState(VM) is the core of reactive processing, I’m going to skip these two functions and focus on initState. Once we get good at this, these two will be a piece of cake.
Vue initialization initState
As usual, the first source code, position the SRC/core/instance/state. Js:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code
An internal array _watchers is initialized to store the observer of the current component instance, and a variable OPS is defined to point to the merged configuration of the current component. If the current component has props, call initProps to initialize props. If methods are configured, call initMethods to initialize methods. If data is configured, call initData to initialize data. Initialize an empty object and observe it. If computed exists, call initComputed to initialize computed; if watch exists and watch is not a native method of Firefox, call initWatch to initialize watch.
Note: The order of initialization is important, indicating that the names of subsequent initializations must not be the same as those already initialized. Because both props, methods, and computed options can eventually be called with this. XXX, duplicate names are bound to cause unexpected problems.
Let’s break down how initProps, initMethods, initData, initComputed, and initWatch initialize data.
initProps
Old rules, first on the source code:
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
constisRoot = ! vm.$parent// root instance props should be converted
if(! isRoot) { toggleObserving(false)}for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if(process.env.NODE_ENV ! = ='production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () = > {
if(! isRoot && ! isUpdatingChildComponent) { warn(`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if(! (keyin vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)}Copy the code
Review: Before we understand how to initialize props, let’s recall the props data format:
props:{
yourPropsName: {type:Number.default:' '}}Copy the code
In the merge section, there is a function normalizeProps(Child, VM) to normalize multiple ways developers can write props into a unified format. So what is the benefit of the unified format purpose? It now emerges: easy initialization and post-processing.
- Let’s start by defining a variable
propsData
Saves component data from outside, or an empty object if none is present. - Define a variable
props
, and define one more on the component instance_props
Property, which have the same reference to an empty object; - Define a variable
keys
As well asvm.$options._propKeys
, also has the same reference to an empty array; - Define a constant
isRoot
Is used to save whether the current component is root. If the current component does not have a parent, it must be the root component. - And then we see
for
To iterate overpropsOptions
The data is executed separately before and after the looptoggleObserving
Function. So what does this function do? It is essentially a switch that is turned on as long as the current component is non-root. It will changesrc/core/observer/index.js
In the fileshouldObserve
Value of a variable. The purpose of this switch is whether it needs to be onprops
Reactive processing. Because I haven’t touched on the response yet, SO I’m not going to talk about itinitData
“Will elaborate.
So here’s what happens in the for loop. The first is to store the props key in the VM.$options._propKeys array. So let’s do this
const value = validateProp(key, propsOptions, propsData, vm)
Copy the code
The main purpose of the validateProp function is to verify that the key value in the props is as expected and return the corresponding value. Take a look at the source code:
export function validateProp (
key: string,
propOptions: Object,
propsData: Object, vm? : Component) :any {
const prop = propOptions[key]
constabsent = ! hasOwn(propsData, key)let value = propsData[key]
// boolean casting
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
if(absent && ! hasOwn(prop,'default')) {
value = false
} else if (value === ' ' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true}}}// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
if( process.env.NODE_ENV ! = ='production' &&
// skip validation for weex recycle-list child component props! (__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
return value
}
Copy the code
initMethods
After the props is initialized, the methods are initialized, which is relatively simple because it does not involve reactive processing
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if(process.env.NODE_ENV ! = ='production') {
if (typeofmethods[key] ! = ='function') {
warn(
`Method "${key}" has type "The ${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly? `,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
}
}
Copy the code
The props variable is defined to point to vm.$options.props, and the methods object is traversed to check each method for compliance in a non-production environment.
- Type check: detects
value
Whether the value is a function. becausemethods
Object must all be functions, otherwise a warning is printed; - Checks whether the current function name matches
props
Naming conflicts in. becauseprops
I’m going to initialize each of themprops
Will be mounted onvm
In both instances, the subsequent initializer needs to ensure that the name does not conflict with the initialized one. For example,props
One of the variables inxxx
, we can pass in the examplethis.xxx
Gets the value in the form of, this time ifmethods
Also appears in axxx
Method overrides the previous oneprops
, so naming rules don’t conflict, that’s one of the rules; - Function naming rule detection: The function name is not
_
or$
Start with a name that does not conflict with a name that already exists on the instance. What does that mean? mean_
or$
isVue
Internal naming conventions are used and are not recommended for developers. If you have to_
or$
The beginning is fine, as long as you don’t$data
These reserved keyword conflicts also work.
The above is just compliance testing, and the next is the highlight, which is why methods can be obtained through this. Iterate over the Methods object, using this of the bind binding function to point to vm, which is the current component instance. A layer of security is added to ensure that the current content must be a function, otherwise only an empty function is bound, ignoring the irregular pattern.
// For example, developers write methods like this
{
methods: {getName: {}}}// We see that getName is an object, not a function
// In the development environment, the browser will print a warning telling the developer not to name it this way, but the developer is lazy enough to handle it
// Vue will initialize getName as a function when building the actual project, but the function is an empty object
// Why do you do this? Because getName is not a function, it can not cause the whole operation
Copy the code
initData
After the methods are initialized, it is time to initialize the data.
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if(! isPlainObject(data)) { data = {} process.env.NODE_ENV ! = ='production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if(process.env.NODE_ENV ! = ='production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if(! isReserved(key)) { proxy(vm,`_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)}Copy the code
$options.data = vm.$options.data = vm.$options.data = vm.$options.data = vm.$options.data = vm. We then define a _data under the instance to hold the converted object of the function data using the getData method
export function getData (data: Function, vm: Component) :any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return{}}finally {
popTarget()
}
}
Copy the code
- We see that the code is mainly contained in one
try... catch...
, this is to prevent the occurrence of an exception error, if the occurrence of an empty object directly returned; - The key to actually getting an object by calling a method is this
data.call(vm,vm)
; getData
Methods start and end with two functionspushTarget()
,popTarget()
This is done to prevent initializationdata
To collect redundant dependencies.
And here’s the passage:
if(! isPlainObject(data)) { data = {} process.env.NODE_ENV ! = ='production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
Copy the code
The isPlainObject method, which we covered in the tools section, is intended for compliance testing to prevent non-pure objects from being obtained through the getData function, in which case an empty object is assigned to data directly and a warning is printed in a non-production environment. The next step is compliance testing and defining properties with the same name as data on the instance object VM to ensure that this. XXX can access the values under the data object.
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if(process.env.NODE_ENV ! = ='production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if(! isReserved(key)) { proxy(vm,`_data`, key)
}
}
Copy the code
Store the key in the data object in the array keys, and then iterate over the data object, this time without a for-in loop. We store the number of array keys in variable I and then iterate over the data object in a while loop decreasing. Three main things have been done:
- 1, make sure
data
In thekey
Values andmethods
The function name defined above in the object is not repeated; - 2, make sure
data
In thekey
Values andprops
Object defined onkey
Values are not repeated; - 3, ensure that
data
Each of the objectskey
Values are named after: not after_
At the beginning or$
At the beginning, because this isVue
Naming rules for.
Proxy (VM,’_data’,key); proxy(VM,’_data’,key); This is how key values in data can be accessed through this. XXX. This time we do not source, first take a simple example, understand this will be able to understand the principle of proxy
let vm = {
data: {name:'wang'.sex:'man'}}console.log(vm.data.name); // wang
console.log(vm.data.sex); // man
Copy the code
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * The answer is simply to use the Object.defineProperty proxy with a value: iterate over vm.data, attach a variable to the VM Object with the same name, and return the value of the vm.data Object with the same name.
for(let key in vm.data){
Object.defineProperty(vm,key,{
get:function(){
return vm.data[key]
}
})
}
Copy the code
This is equivalent to iterating over every value in the vm.data object and making a copy to the VM object. Just print the VM object and see what happens
{
data: {name:'wang'.sex:'man'
},
name:'wang'.sex:'man'
}
Copy the code
The principle is already clear, so let’s continue to look at the proxy call method:
proxy(vm,'_data',key)
Copy the code
Call (vm,vm) to get a pure object and assign the value to vm._data, so the data in the developer instance is probably in this format:
const vm = {
data(){
return {
name:'wang'.sex:'man'}},_data: {name:'wang'.sex:'man'}}Copy the code
It takes three parameters:
target
That’s our component instance;sourceKey
In the example_data
;key
is_data
Each of the objectskey
Value.
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code
Looking back at the while loop in initData, each time the loop executes to the proxy, it looks something like this
Object.defineProperty(vm,'name', {get:function(){
return vm._data.name
},
set:function(val){ vm._data.name = val; }})Copy the code
Here’s where the data really gets responsive:
observe(data,true)
Copy the code
Observe source code: core/observer/index.js
export function observe (value: any, asRootData: ? boolean) :Observer | void {
if(! isObject(value) || valueinstanceof 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
}
Copy the code
The observe function takes two arguments:
- 1, the object to be responded to (required), format is
Array
orObject
. - 2. A Boolean value that indicates whether the component is root (not required).
The real responsive processing is in the Observer class. Observe can be considered an entry point to the Observer and does some normative processing. Here is a list of situations in which the response will be made and situations in which the response will be avoided:
- 1, if not pure array or pure object, return null, do not continue execution;
- 2. If yes
VNode
, also returns null and does not continue execution; - 3. If the array or object currently being observed has
__ob__
Property, and__ob__
isObserver
Class, which means currentvalue
Has been processed by response, in order to avoid repeated observation, it is returned directlyvalue.__ob__
; - 4. Responsivity is executed if and only if these five conditions are met:
shouldObserve
fortrue
); 2. Non-server rendering; Array or pure object; Four, the currentvalue
Extensible; Five,value
nonVue
Instance.
Source code: core/ Observer /index.js
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])
}
}
}
Copy the code
After all this time, the Observer constructor is the beginning of the real transformation of data into a responsive form. It has three instance attributes: Value, DEp, vmCount, and two methods: Walk, observeArray. It’s a little tedious to talk about the concept, but let’s continue with observing an object:
const data = {
name:'wang'
}
new Observer(data)
Copy the code
We know that when new is instantiated, the constructor method is executed by default, which adds an __ob__ object that is not enumerable to the current object. In order not to interfere with actual development, such as for.. in.. The object has a value attribute pointing to the currently observed object, a DEp attribute, which is a box for collecting and saving dependencies, and a vmCount attribute, which defaults to 0. The general format of the observable object after instantiation is as follows:
{
name:'wang'.__ob__: {// __ob__ is seen as translucent under chrome debugger because it is not enumerable
value:data,
dep:new Dep(),
vmCount:0}}Copy the code
The next step is to determine whether the value of the observed object is an array or an object. If the value is an array, call observeArray method to observe in response. This method is very simple. If the object is called walk method for reactive observation, the principle is also very simple, for loop through each item of the object, using defineReactive method observation, the specific principle will be described in detail later. Before continuing with the response observation, let’s focus on this paragraph:
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
Copy the code
When the object is an array, there is a judgment. HasProto, described in the tooltip, is a Boolean value with the source code ‘__proto__’ in {}, used to determine whether the current host environment supports the __proto__ attribute. We know that __proto__ of an instance tends to refer to a prototype object, for example:
var arr = new Array(a);console.log(arr.__proto__ === Array.prototype); // true
Copy the code
So when the object is Array, it should be Array. Prototype, if… else… The purpose of arrayMethods is to point to the prototype of the array to be observed. If the current host environment supports the __proto__ attribute, execute protoAugment(value, arrayMethods) to modify the pointing of the prototype chain, otherwise call copyAugment(value, arrayMethods, arrayKeys) to modify the pointing of the prototype chain. So let’s look at the source of these two methods:
function protoAugment (target, src: Object) {
target.__proto__ = src
}
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])
}
}
export function def (obj: Object, key: string, val: any, enumerable? : boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable:!!!!! enumerable,writable: true.configurable: true})}Copy the code
The protoAugment method is simple, directly modifying the __proto__ attribute of the instance, i.e. Value.__proto__ = arrayMethods. If the current host environment does not support __proto__, the copyAugment method is called, where each key is iterated over and the specified method is intercepted via Object.defineProperty.
The implementation of the array interception method
An array is a special data structure. It has many methods, and some of them modify the array itself. These methods are called mutating methods. Vue needs to be informed and react in a timely manner. So how does Vue do it? Let’s look at the following source SRC/core/observer/array. Js
import { def } from '.. /util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
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
})
})
Copy the code
ArrayProto refers to array. prototype, and arrayMethods refers to array. prototype via object. create. I then define the array methodsToPatch to hold the execution of the variable method traversal of the seven arrays. ArrayMethods is an empty object until the forEach traverse is executed, and a variant method is mounted to the arrayMethods object with each traverse. Let’s take a look at what forEach traversal does.
- Let’s start by defining a variable
original
Caching native array methods (after all, you can’t change the way arrays function); - We see first
def
In themutator
Method, which starts with execution firstconst result = original.apply(this, args)
Get results to achieve practical goals, and finally inreturn result
Returns the result. Although it is an interception, it obviously does not affect the native methods of arrays; - The key is in the middle paragraph
ob.dep.notify()
Will be called__ob__.dep
thenotify
To inform its subscribers, saying: I have changed, you quickly update; - In addition to this, there is a section in the middle that determines whether the array has new content, and if so, the new content needs to be called
observeArray
Method is converted to reactive.
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
Copy the code
Now that we’ve figured out how to intercept arrays, let’s look at the responsivity principle for pure objects
The responsivity principle of pure objects
Return to the Observer constructor, which executes this.walk(value) when the observed is a pure object.
{
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
Copy the code
Let’s take a pure object observation example:
const obj = {
a:1.b:2
}
Copy the code
When executing a walk, you do this separately:
defineReactive(obj,'a');
defineReactive(obj,'b');
Copy the code
DefineReactive: core/observer/index.js
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]
}
letchildOb = ! 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() } }) }Copy the code
The core of this function is to convert the data properties of the object to accessor properties by setting a pair of getters/setters for the properties of the data object. The function takes a maximum of five arguments. We define a constant dep = new dep () that is referenced by the closure in the getter/setter of the accessor property. We know that each data field in the data object refers to its own DEP constant through the closure. The Dep object for each field is used to collect the dependencies that belong to the corresponding field. This is followed by code like this:
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
Copy the code
Define variable property to hold the description object of the current property. If the description of the current property cannot be changed or the property cannot be deleted, the object is returned and the execution stops.
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
Copy the code
Then cache the get and set methods that may exist in the original description property, and cache them in the getter and setter variables respectively. Why cache them? Object. DefineProperty is then executed to redefine the getter/setter for the property, which will cause the original get and set methods to be overwritten. The next problem is boundary-handling, redefining the value of val when only two parameters are passed and there is no getter or setter. Here is the code:
letchildOb = ! shallow && observe(val)Copy the code
(shallow is false) Shallow is true and val is deeply observed. By default, shallow is not passed (i.e., undefined), which means that depth observation is enabled by default.
Now let’s look at the interceptor’sget
What does the method do?
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
}
Copy the code
Because it is the getter that reads the property, you need to ensure that the normal logic is not affected, and you need to return the correct result, so you first get the result and save it in the variable value, and finally return the correct value of the property. In addition to returning the correct property value, which is collecting dependencies, this is the main purpose of the getter. Let’s look at what the if statement does. If dep.target is true, execute dep.depend(). So what is dep.Target? The dep constructor () is not yet in use, so it is assumed that the dep constructor is a collection of dependencies. We will cover the dep constructor later.
Dep.depend () :
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
Copy the code
That is, if childOb exists, the dependency will be deeply collected. Default defineReactive pass is not shallow parameter, then childOb value is to observe (val) return values, we know the key: object is the value of the format, if the value is the basic data types, although performed observe method, But recall the first sentence of the Observe method:
if(! isObject(value) || valueinstanceof VNode) {
return
}
Copy the code
If it’s not an array or a pure object, it just returns and doesn’t execute. For pure objects, such as this format:
const data = {
a:1.b: {c:3.__ob__:{
dep,
value,
vmCount
}
},
__ob__:{
dep,
value,
vmCount
}
}
Copy the code
DefineReactive (data,’b’) {c:3} childOb = __obj__:{dep,value,vmCount} Then childob.dep.depend () is equivalent to data.b.__ob__.dep.depend() for deep dependency collection. The next step is to determine the value type and invoke the dependArray function on each element of the array individually.
That’s how dependencies are collected in GET. Let’s look at how dependencies are triggered in set, and how observers are notified to update data when properties are changed.
Here are the interceptorsset
Function source:
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() }Copy the code
The set function does two things: 1. Correctly sets the new value for the property. 2. Trigger corresponding dependencies;
First, these two lines of code:
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
Copy the code
The original value of the acquired attribute is saved in the variable value. If the new value is identical with the old value, no action is required and the value is returned directly. The other case is a bit more convoluted, but is mainly to deal with cases where both the old and new values are NaN. We know that NaN is not equal to itself in the browser
console.log(NaN= = =NaN); // false
Copy the code
The next step is to execute customSetter() in a non-production environment if the customSetter parameter exists. You can think of it as a secondary callback function that is executed only in a non-production environment.
Here is the code:
if(getter && ! setter)return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
Copy the code
If only a getter exists and no setter exists, return the getter directly. And then there’s an if… else… The purpose is to set the property value correctly. Setter constants hold the set function originally owned by the property. If it exists, the setter will call the original method to set the new property. If it does not exist, the setter will directly set val = newVal.
The last two lines of code look like this:
childOb = ! shallow && observe(newVal) dep.notify()Copy the code
By default, the shallow value does not exist, which means that the observe method is executed to observe the depth of the newly added value. And finally, the notification dependency, which tells the observer: I’m updating, so you should follow suit.