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 variablepropsDataSaves component data from outside, or an empty object if none is present.
  • Define a variableprops, and define one more on the component instance_propsProperty, which have the same reference to an empty object;
  • Define a variablekeysAs well asvm.$options._propKeys, also has the same reference to an empty array;
  • Define a constantisRootIs 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 seeforTo iterate overpropsOptionsThe data is executed separately before and after the looptoggleObservingFunction. 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.jsIn the fileshouldObserveValue of a variable. The purpose of this switch is whether it needs to be onpropsReactive 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: detectsvalueWhether the value is a function. becausemethodsObject must all be functions, otherwise a warning is printed;
  • Checks whether the current function name matchespropsNaming conflicts in. becausepropsI’m going to initialize each of thempropsWill be mounted onvmIn both instances, the subsequent initializer needs to ensure that the name does not conflict with the initialized one. For example,propsOne of the variables inxxx, we can pass in the examplethis.xxxGets the value in the form of, this time ifmethodsAlso appears in axxxMethod 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$isVueInternal 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$dataThese 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 onetry... 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 thisdata.call(vm,vm);
  • getDataMethods start and end with two functionspushTarget(),popTarget()This is done to prevent initializationdataTo 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 suredataIn thekeyValues andmethodsThe function name defined above in the object is not repeated;
  • 2, make suredataIn thekeyValues andpropsObject defined onkeyValues are not repeated;
  • 3, ensure thatdataEach of the objectskeyValues are named after: not after_At the beginning or$At the beginning, because this isVueNaming 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:

  • targetThat’s our component instance;
  • sourceKeyIn the example_data;
  • keyis_dataEach of the objectskeyValue.
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 isArrayorObject.
  • 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 yesVNode, also returns null and does not continue execution;
  • 3. If the array or object currently being observed has__ob__Property, and__ob__isObserverClass, which means currentvalueHas 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:shouldObservefortrue); 2. Non-server rendering; Array or pure object; Four, the currentvalueExtensible; Five,valuenonVueInstance.


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 variableoriginalCaching native array methods (after all, you can’t change the way arrays function);
  • We see firstdefIn themutatorMethod, which starts with execution firstconst result = original.apply(this, args)Get results to achieve practical goals, and finally inreturn resultReturns the result. Although it is an interception, it obviously does not affect the native methods of arrays;
  • The key is in the middle paragraphob.dep.notify()Will be called__ob__.depthenotifyTo 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 calledobserveArrayMethod 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’sgetWhat 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 interceptorssetFunction 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.