When instance objects are created on Vue, data, props, computed, and watch are set to reactive objects

These processes occur in initState (vm), it is defined in SRC/core/instance/state. Js

export function initState (vm: Component) {
  vm._watchers = []
  / / get the options
  const opts = vm.$options
  / / processing props
  if (opts.props) initProps(vm, opts.props)
  / / processing methods
  if (opts.methods) initMethods(vm, opts.methods)
  / / the data processing
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)}/ / processing computed
  if (opts.computed) initComputed(vm, opts.computed)
  / / processing watch
  if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code

The initState function processes props, methods, data, computed, and watch in sequence

Let’s talk about how DATA responds, and write a separate article about the others

Data response principle

During the execution of initState, if opts.data exists, initData is called to initialize the data and turn it into a responsive object

initData

InitData functions defined in SRC/core/instance/state. Js

function initData (vm: Component) {
  let data = vm.$options.data
  // if data is a function, execute the function first, get the return value and assign it to vm._data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // Ensure that there are no properties of the same name in methods, props, and data
  while (i--) {
    const key = keys[i]
    if(process.env.NODE_ENV ! = ='production') {
      // If there is a key error in the methods
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`, vm)
      }
    }
    // If the props contains a key error
    if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(/ *... * /)}else if(! isReserved(key)) {// enable developers to access properties in data directly through this.xxx
      proxy(vm, `_data`, key)
    }
  }
  // Add a response to data
  observe(data, true /* asRootData */)}Copy the code

InitData does three things in total:

  • The first thing we do is verifymethods,props,dataThere is no property of the same name in
  • throughproxydataIn thekeyThe agent tovmUp, so you can get throughthis.xxxAccess modedataAttribute in;
  • throughobserveThe observer () function creates an observer instance and givesdataAll properties add a response

proxy

const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: noop,
  set: noop
}
// Set the proxy to proxy key to target
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

observe

Defined in the/SRC/core/observer/index in js

export function observe (value: any, asRootData: ? boolean) :Observer | void {
  // Non-objects and VNode instances do not respond
  if(! isObject(value) || valueinstanceof VNode) {
    return
  }
  let ob: Observer | void
  // If the value object has an __ob__ attribute, the response has been added, and the __ob__ attribute is returned
  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 ) {// Create an observer instance
    ob = new Observer(value)
  }
  // asRootData is true if the object that is currently adding the response is data
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
Copy the code

The observe function takes two arguments

  • valueThe object to which to add the response
  • asRootDataIf the object to which the response is added isdata.asRootDatatrue

If shouldObserve is true and value is a non-VNode object, then create an Observer instance from the Observer, and return the existing Observer instance if the object has already been observed

Observer

Defined in the/SRC/core/observer/index in 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
    // Create a Dep instance
    this.dep = new Dep()
    this.vmCount = 0
    // Mount an instance of itself to value.__ob__
    def(value, '__ob__'.this)
    // Array processing logic
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      // If value is an object
      this.walk(value)
    }
  }
  // Iterate over all properties and add a response to the property
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  /** * Iterates through the array. If the array elements are objects, create an observer instance for that object and add a response */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
Copy the code

The Observer mounts this to value.__ob__ during instantiation; That is, responsive objects have a __ob__ attribute that points to the Observer instance. An instance of dep is also mounted on this.dep;

The next step is to determine whether it is an array. If it is an array, it will follow the array processing logic (later). In the case of an object, walk is called through each property of the object, setting getters and setters for each property through the defineReactive method

defineReactive

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
  // Instantiate dep with one key for each DEp
  const dep = new Dep()
  Return obj[key]; return obj[key]
  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]
  }
  // This recursive call handles the case where val (obj[key]) is an object, ensuring that all keys in the object are observed
  letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      // ...
      const value = getter ? getter.call(obj) : val
      // ...
      return value
    },
    set: function reactiveSetter (newVal) {/ *... * /}})}Copy the code

DefineReactive function

  • Instantiate aDepObject, akeyCorresponds to aDepThe instance
  • Then getobjAttribute descriptor to determine whether it is configurable;
  • Recursive calls to child objectsobserveMethod, so as to ensure that no matterobjAll of its child properties can also become reactive objects that can be accessed or modifiedobjOne of the more deeply nested properties can also triggergettersetter.
  • usingObject.definePropertyLet’s go toobjThe properties of thekeyaddgettersetter

Array response Principle

There is the following logic in the Observer

if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  this.observeArray(value)
} else {
  // If value is an object
  this.walk(value)
}
Copy the code

During the instantiation of the Observer, it determines whether value is an array. If it is an array, it first determines whether the current environment has an __proto__ attribute. If so, protoAugment is executed

// can we use __proto__?
export const hasProto = '__proto__' in {}
Copy the code

Look at arrayMethods before looking at protoAugment and copyAugment

ArrayMethods defined in SRC/core/observer/array. Js

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (. args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    // Method to add elements
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2) // The splice method has new elements after the second argument
        break
    }
    // Set the response to the new element
    if (inserted) ob.observeArray(inserted)
    // Trigger dependency notifications manually
    ob.dep.notify()
    return result
  })
})
Copy the code

Vue overrides some of the methods of the array, and then mounts the overridden methods onto arrayMethods. Rewrite the idea as follows:

  • When an array method is called, the native method is executed first, getting the return value of the native functionreslut;
  • In the case of a new method, a response is added to the new element, provided that the new element is an object
  • Triggers the current arraydep.notifynoticeWatcherupdate
  • Returns the return value of the native functionreslut

After you know the content of arrayMethods, look at protoAugment and copyAugment, these two functions are relatively simple, do not show the code, directly say what to do

  • protoAugmentwillarrayMethodsIt’s assigned to an array__proto__attribute
  • copyAugmenttraversearrayKeys(arrayMethodsProperty name collection), willarrayMethodsAll properties are mounted to the array

What that means is that when the object being listened on is an array, Vue overrides some of the methods of the array, and then mounts those overridden methods onto the array. When these methods are called, the original method is executed first, the return value is obtained, and if it is a new property, the response is added to the new property; Finally, manually trigger the update.

The point to note here is that deP is the array deP, and an instance of deP is also created during the instantiation of the Observer

The flowchart for adding a response is as follows

Depend on the collection

Before looking at getters, let’s look at Dep and Watcher

Dep

import type Watcher from './watcher'
import { remove } from '.. /util/index'
import config from '.. /config'

let uid = 0

export default class Dep {
  statictarget: ? Watcher; id: number; subs:Array<Watcher>;
  
  constructor () {
    this.id = uid++
    // Initialize the subs array to store watchers that depend on this property
    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 () {
    const subs = this.subs.slice()
    if(process.env.NODE_ENV ! = ='production' && !config.async) {
      subs.sort((a, b) = > a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Dep.target = null
const targetStack = []
// Change the value of dep. target so that the value of dep. target is the Watcher being updated.
// and adds the Watcher currently being updated to the targetStack
export function pushTarget (target: ? Watcher) {
  targetStack.push(target)
  Dep.target = target
}
// Delete the last element in the targetStack and change the value of dep. target (out of the stack)
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]}Copy the code

Dep is a Class that defines properties and methods

  • Dep.target: This is a global uniqueWatcherThere can only be one global at a timeWatcherBy calculating
  • this.subsInitialization:subsArray to store items that depend on this propertyWatcher
  • addSub: will depend on this attributeWatcherAdded to thesubsTo do dependency collection
  • removeSub: deletesubsIn theWatcher
  • notify: traversalsubsAnd execute the elementupdateMethod, to informWatcherupdate

Watcher

The Watcher instantiation process is more complex and will be explained in detail in the dependency collection process

Rely on the collection process

$mount calls mountComponent to create a exporcher, pass updateComponent, which is the second argument of class Watcher

// ...
updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
// ...
new Watcher(vm, updateComponent, noop, {
  before () {
    if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher will be true */)
Copy the code

Look at Watcher’s definition

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.vm = vm
    // For render Watcher, isRenderWatcher is true
    if (isRenderWatcher) {
      // Only render Watcher will mount itself to vm._watcher
      vm._watcher = this
    }
    vm._watchers.push(this)
    if (options) {
      // Initialize the Watcher properties, which are false for rendering Watcher
      this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.sync// Rendering Watcher's before function performs the beforeUpdate lifecycle
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid
    this.active = true
    this.dirty = this.lazy
    this.deps = []
    this.newDeps = []
    this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production'
      ? expOrFn.toString()
      : ' '
      // Render Watcher's expOrFn is updateComponent, which is a function
    if (typeof expOrFn === 'function') {
      // Assign updateComponent to the getter property
      this.getter = expOrFn
    } else {}
    The render Watcher's lazy property is false, so the get method is executed during initialization
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  get () {}
  addDep (dep: Dep) {}
  cleanupDeps () {}
  update () {}
  run () {}
  evaluate () {}
  depend () {}
  teardown () {}
}
Copy the code

When initializing the render Watcher, there are several points to note

  • vm._watcher = thisMount itself tovm._watcheron
  • Initialize four properties
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)Copy the code
  • willupdateComponentAssigned togetterattribute
  • performthis.get()methods

Look at the get method

get () {
    // Assign dep. target to the current Watcher
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // Execute the getter method, which executes the updateComponent method
      value = this.getter.call(vm, vm)
    } catch (e) {
    } finally {
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
}
Copy the code

Call pushTarget to point dep. target at the current render Watcher. Call this.getter to execute the _render method. During execution of the _render method, the attribute value of the responsive property is fetched, which triggers the getter method of the property to do dependency collection

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
  const dep = new Dep()
  / / Object. GetOwnPropertyDescriptor return objects on a has its own corresponding attribute descriptor
  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 () {
      // Get the current value
      const value = getter ? getter.call(obj) : val
      // Add the possibility of judgment here
      // 1. When you modify the value of an attribute, you may get the value of another attribute, so this can prevent the collection of dependencies again
      // 2. Prevent dependency collection when lifecycle functions are executed
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          // The purpose of this is
          // 1. The view can be updated when certain methods of the array are executed
              // Since calling some methods on the array actually triggers the update manually, but the Watcher stored in the array dep is updated, the Watcher needs to be stored in the array deP instance during the dependency collection process
          // 2. When you add properties to an object, you can update the view (for the same reason as in # 1).
          childOb.dep.depend()
          if (Array.isArray(value)) {
            // If it is an array and there are objects in the array elements, add the Watcher to the object's dep.subs
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {/ *... * /}})}Copy the code

The getter first determines whether the dep. target exists, in which case dep. target points to the current rendering Watcher, and then dep.depend(), which executes Watcher’s addDep method

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)}}}Copy the code

Dep. AddSub (this) will execute this.subs.push(sub), adding the current Watcher to the subs of the deP that holds the data. The purpose is to prepare which subs can be notified in the event of subsequent data changes

Back to the getter method, if childOb is an Observer instance, add the current Watcher to the subs of Childob.dep; If the value is an array, run a dependArray dependArray, which adds Watcher to the dep.subs of the object if there is an object in the array.

After the dependency collection is complete, go back to Watcher’s GET method and execute popTarget() to recover the dep.target value, then execute the cleanupDeps cleanup process. Compare newDeps to deps. If deps does but newDeps does not, the new VNode is no longer dependent on the current properties, and the current watcher is removed from the subs of the dep

  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
  }
Copy the code

After dependency collection is complete, VNode is created and enter the patch process rendering node.

Distributed update

The purpose of dependency collection is to distribute updates to related dependencies when data is modified

When the value of a property is modified, the setter method for the property is executed

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
  const dep = new Dep()
  / / Object. GetOwnPropertyDescriptor return objects on a has its own corresponding attribute descriptor
  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 () {},
    set: function reactiveSetter (newVal) {
      // Get the old attribute value
      const value = getter ? getter.call(obj) : val
      // If the values of the new and old attributes are equal, return directly
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      // In the development environment, execute the customSetter if it is passed in
      // There is only one case in which an error is reported for props
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }
      if(getter && ! setter)return
      // Set the new value
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // Add a response to the new value if it is an objectchildOb = ! shallow && observe(newVal)// Notify all subscribers of the update
      dep.notify()
    }
  })
}
Copy the code

The setter method first checks whether the old and new property values are equal, and if they are not, sets the new value and executes dep.notify() to notify all subscribers of the update

Dep. notify is a method of the dep class:

 notify () {
    const subs = this.subs.slice()
    if(process.env.NODE_ENV ! = ='production' && !config.async) {
      subs.sort((a, b) = > a.id - b.id)
    }
    // Trigger the update method for each Watcher in subs
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

During dependency collection, all watchers that depend on this attribute are added to subs. Dep. notify traverses subs and triggers the update method of each Watcher

  update () {
    // Render Watcher's lazy is false, as shown in the section calculating properties
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      // Render Watcher's sync is also false, as shown in the Watch section
      this.run()
    } else {
      // Render Watcher to execute queueWatcher
      queueWatcher(this)}}Copy the code

The code is simple, in the case of rendering Watcher, executing queueWatcher methods

QueueWatcher methods defined in SRC/core/observer/scheduler. Js

const queue: Array<Watcher> = [] // Queue, store Watcher to execute
let has: { [key: number]: ?true } = {} // Make sure the same Watcher is added only once
let waiting = false // Ensure that nextTick(flushSchedulerQueue) is called only once at a time
let flushing = false
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)
    }
  }
}
Copy the code

The queueWatcher method first verifies that the incoming Watcher is in the queue, ensuring that the same Watcher is added only once. Then if flushing is false, then add Watcher to the queue; Now the flushing is true and we’ll talk about it in the watch section; Next call nextTick(flushSchedulerQueue) if waiting is false and there is no nextTick waiting at the current time

Vue does not trigger the watcher callback every time the data changes. Instead, Vue adds the watcher to a queue first and executes the flushSchedulerQueue after nextTick

Then see flushSchedulerQueue implementation, it is defined in SRC/core/observer/scheduler. Js

export const MAX_UPDATE_COUNT = 100
let circular: { [key: number]: number } = {}

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  // Order the Watcher in the queue from smallest to largest by ID
  queue.sort((a, b) = > a.id - b.id)

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      // Render Watcher has before methods that execute beforeUpdate hooks, before parent after child
      watcher.before()
    }
    id = watcher.id
    // Change the id in has to null
    has[id] = null
    // Call the watcher.run method to trigger the update
    watcher.run()
    if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
      // For each watcher, circular[id] is added by 1
      circular[id] = (circular[id] || 0) + 1
      // When the number of executions is greater than 100, an error is reported and the loop is executed
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn()
        break}}}// ...
  
  const updatedQueue = queue.slice()
  
  // Empty queue, has, circular, and set to false for waiting and flushing
  resetSchedulerState()
  // ...
  
  callUpdatedHooks(updatedQueue)
}
Copy the code

When performing to the queue and executes flushSchedulerQueue function, first on the queue Watcher sorting, since the childhood by id:

    1. Component updates are from parent to child. (Because the parent component is always created before the child component)
    1. The component’sUser WatcherinRender WatcherBefore executing (because it was initialized firstwatchIs created during initializationUser Watcher)
    1. If a component is destroyed during the execution of the parent’s Watcher, its Watcher execution can be skipped, so the parent’s Watcher should be executed first

It then traverses the queue and executes the watch.before method, which executes the component’s beforeUpdate lifecycle function. Change the ID in has to null so that the Watcher with the current ID can be added to the queue again during all Watcher updates (such as changing the value of a responsive property in a listener). The update is then triggered by a call to watcher.run().

  run () {
    if (this.active) {
      // Trigger Watcher's get method
      const value = this.get()
      if( value ! = =this.value ||
        isObject(value) ||
        this.deep
      ) { / *... * /}}}Copy the code

For rendering Watcher, the watcher.run() method executes the Watcher class’s get method to trigger the component update. Since rendering Watcher’s GET method returns undefined, the remaining if logic does not execute. This logic is related to computing properties, watches. FlushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; FlushSchedulerQueue function executes resetSchedulerState to empty the queue, has, circular, and will be waiting, flushing to false. Then call the callUpdatedHooks method.

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    // Only render Watcher will bind itself to vm._watcher
    if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) { callHook(vm,'updated')}}}Copy the code

CallUpdatedHooks specifies the updated lifecycle of a component. Note that the order of execution is child first and parent second. If vm._watcher === WATcher, the component has executed the Mounted life cycle, and the component is not unmounted. This concludes the distribution of updates.

conclusion

The responsive principle of Vue is to intercept data access and modification through object.defineProperty; When executing a component render function, if a responsive property is used, the getter for that property is triggered and the component’s Render Watcher is added to the property’s dep.subs. This process is called dependency collection.

When a responsive property is modified, the setter is triggered to notify all Watcher updates in dep.subs, thus rerendering the component.

Schematic of the response on the official website

Array response

For arrays, Vue overrides seven methods; When the array data is fetched, childob.dep.depend () is called in the getter, adding the current Watcher to this.dep in the array Observer instance. When the overridden method is called, all Watcher updates in the dep.subs of the array Observer instance are manually notified

Vue.prototype.$set

Add a property to the responsive object and ensure that the new property is also responsive and triggers the view update. It must be used to add new properties to reactive objects

View updates are also triggered when object or array data is modified via this.$set, for example

this.$set(this.arr, 1.'Modify array elements')
this.$set(this.obj, 'test'.'Modify object properties')
Copy the code

Let’s look at the code for set

export function set (target: Array<any> | Object, key: any, val: any) :any {
  // ...
  
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // In the case of an array, call splice to trigger the update
    target.splice(key, 1, val)
    return val
  }
  // If the target already has a key, it is a responsive attribute and no further action is required
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  
  // ...
  
  // Target is not an Observer instance
  if(! ob) { target[key] = valreturn val
  }
  // Add the new key to ob.value and add the response
  defineReactive(ob.value, key, val)
  // Trigger dependency notifications manually
  ob.dep.notify()
  return val
}
Copy the code

The vue.prototype. $set method also manually triggers notifications to update the view and adds responses to new properties.

If target is an array, then the splice method of the array is called, because Vue overwrites the splice method of the array, and all updates are triggered manually in the overwritten splice method; If the new element is an object, the response is added to that object;

If the target is a responsive object, the __ob__ attribute (with the value of an Observer instance) is obtained, and the new attribute is added to the target with defineReactive and the response is added. Then trigger the update manually.

Why is data in a component a function

When a component is reused multiple times, multiple instances are created. Each instance will retrieve data from options during initialization. If data is an object, each component instance will point to the same data, causing a conflict. So if it’s a function that returns an object, that solves the problem.