As mentioned before, the Object. DefifineProperty () is used for Vue’s responsive processing of data during initialization. The getter method for Object attributes is defined to intercept the access to Object attributes, and the dependency collection is used.

Inform the update

setter

Intercepting setter functions are triggered when reactive data changes. Let’s start with setters:

// src/core/observer/index.js
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean{
  // ...
  Object.defineProperty(obj, key, {
    enumerabletrue.configurabletrue.// ...
    // Hijack the modify operation
    setfunction reactiveSetter (newVal{
      [key] / / the old obj
      const value = getter ? getter.call(obj) : val
      // If the old and new values are the same, return without updating
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      /* eslint-enable no-self-compare */
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }
      // If the setter does not exist, the property is read-only and returns directly
      if(getter && ! setter)return
      // Set the new value
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // Observe the new value so that it is also responsivechildOb = ! shallow && observe(newVal)// Rely on notification updates
      dep.notify()
    }
  })
}
Copy the code

dep.notify()

  // src/core/observer/dep.js
  // Notification update
  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()
    }
  }
Copy the code

To traverse the watcher stored in the DEP, execute watcher.update().

watcher.update()

// src/core/observer/watcher.js
export default class Watcher {
  // ...
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      // Go here for lazy execution, such as computed watcher
      // Set dirty to true, and the evaluation of the calculated attributes is recalculated
      this.dirty = true
    } else if (this.sync) {
      $watch = vm.$watch = vm.$watch
      // When true, the watcher does not run through the asynchronous update queue and executes the this.run method to update data
      // This attribute does not appear in the official documentation
      this.run()
    } else {
      // Put watcher in the watcher queue
      queueWatcher(this)}}}Copy the code

queueWatcher

// src/core/observer/scheduler.js
const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
/** * Put watcher in queue */
export function queueWatcher (watcher: Watcher{
  const id = watcher.id
  // If watcher already exists, skip it
  if (has[id] == null) {
    // Caches watcher. Id to determine whether watcher is in the queue
    has[id] = true
    if(! flushing) {// The queue is not refreshed. Watcher joins the queue
      queue.push(watcher)
    } else {
      // Flushing the queue, the user might add a new Watcher and go here
      // Find the first position where watcher.id is greater than the watcher.id in the current queue, and insert yourself into that position. Keep the queue in order.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
    // Waiting ensures that nextTick is called only once
    if(! waiting) { waiting =true
      if(process.env.NODE_ENV ! = ='production' && !config.async) {
        // Refresh the scheduling queue directly
        Vue is executed asynchronously by default. If you change it to synchronous execution, performance will suffer
        flushSchedulerQueue()
        return
      }
      // nextTick => vm.$nextTick, vue.nexttick
      nextTick(flushSchedulerQueue)
    }
  }
}
Copy the code

NextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue nextTick flushSchedulerQueue

Let’s see what flushSchedulerQueue does:

flushSchedulerQueue

// src/core/observer/scheduler.js
function flushSchedulerQueue ({
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  // Sort the queue from smallest to largest.
  // 1. The component is updated from parent to child. Since the parent component is created before the child component, the watcher is also created after the parent component.
  // 2. The user watcher of a component is executed before the render watcher, and the user Watcher is created before the render watcher.
  // 3. If a component is destroyed during the parent component's Watcher execution, its corresponding Watcher execution can be skipped, so the parent component's Watcher should be executed first.
  queue.sort((a, b) = > a.id - b.id)
  // Queue.length is evaluated each time through the loop, because at watcher.run() it is likely that the user will add a new watcher
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    Execute beforeUpdate lifecycle hooks that are passed in when Watcher is created in the mount phase
    if (watcher.before) {
      watcher.before()
    }
    // Clear the cached watcher
    id = watcher.id
    has[id] = null
    // Execute watcher.run to trigger the update function
    watcher.run()
    // in dev build, check and stop circular updates.
    if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break}}}// Hold a copy of the queue until the state is reset
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  // Reset refresh queue status
  resetSchedulerState()
  // The keep-alive component is related
  callActivatedHooks(activatedQueue)
  // Executes the updated lifecycle hook
  callUpdatedHooks(updatedQueue)
  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')}}/** * Return some of these variables to their original values and clear the Watcher queue. * /
function resetSchedulerState ({
  index = queue.length = activatedChildren.length = 0
  has = {}
  if(process.env.NODE_ENV ! = ='production') {
    circular = {}
  }
  waiting = flushing = false
}

/** * The updated lifecycle hook */ is executed from child to parent
function callUpdatedHooks (queue{
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) { callHook(vm,'updated')}}}Copy the code

FlushSchedulerQueue executes an update queue. Trigger the final update with watcher.run().

watcher.run()

// src/core/observer/watcher.js
export default class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.cb = cb
  } 
  run () {
    if (this.active) {
      // Call the this.get method
      const value = this.get()
      if( value ! = =this.value ||  // New and old values are not equal
        isObject(value) ||   // New values are objects
        this.deep   / / deep patterns
      ) {
        // Update the old value to the new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          // If the user is watcher
          const info = `callback for watcher "The ${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          // Render watcher, this.cb = noop, an empty function
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
}
Copy the code

There are two cases in which this.user represents the user watcher when it is true, which was described earlier as the user Watcher, otherwise the logic of rendering Watcher is executed.

  • user watcher

The first argument received by invokeWithErrorHandling is our custom listener callback. During initialization of the listener initWatch method, New Watcher(VM, expOrFn, CB, options) is passed in for instantiation. The third argument is [value, oldValue] (new value and oldValue), which is why new and old values can be obtained in callback functions that listen for properties.

// src/core/util/error.js
export function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
{
  let res
  // Use try catch to do some error handling
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if(res && ! res._isVue && isPromise(res) && ! res._handled) { res.catch(e= > handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      res._handled = true}}catch (e) {
    handleError(e, vm, info)
  }
  return res
}
Copy the code
  • Render the watcher

Call this.cb.call(this.vm, value, oldValue) if watcher is rendered. Render Wather instantiation is performed in the mountComponent method at mount time:

  // src/core/instance/lifecycle.js
  new Watcher(vm, updateComponent, noop, {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
Copy the code

export function noop (a? : any, b? : any, c? : any) {} is an empty function, so this.cb.call(this.vm, value, oldValue) is executing an empty function.

The render watcher will call this.get() when watcher.run is executed, which in turn will call this.getter.call(vm, vm). Getter is actually the second parameter updateComponent passed in to instantiation.

// src/core/instance/lifecycle.js
updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
Copy the code

So this is why when we modify the responsive data related to the component, the component re-rendering will be triggered, and then the patch process will be entered.

nextTick

FlushSchedulerQueue (flushSchedulerQueue) flushSchedulerQueue (queueWatcher) flushSchedulerQueue (queueWatcher)

nextTick(flushSchedulerQueue)
Copy the code

nextTick

// src/core/util/next-tick.js
const callbacks = []
let pending = false

export function nextTick (cb? :Function, ctx? :Object{
  let _resolve
  // Store wrapped CB functions with callbacks arrays
  callbacks.push(() = > {
    if (cb) {
      // Wrap callback functions with try catch to facilitate error catching
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    timerFunc()
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code

The first argument to nextTick is a callback that corresponds to flushSchedulerQueue. The callback function is wrapped with a try catch for error catching, and then put into the Callbacks.

The reason for using callbacks instead of executing callbacks directly on nextTick is to ensure that executing nextTick multiple times does not start multiple asynchronous tasks, but instead pushes them into a single synchronous task that will be executed on the nextTick.

TimerFunc is then executed when pending is false. Pending is true, indicating that the task is being placed in the browser’s task queue. Pending is false, indicating that the task has been placed in the browser task queue.

Finally, nextTick returns a promise providing a call to.then when no CB callback is passed in.

nextTick().then(() = > {})
Copy the code

timerFunc

// src/core/util/next-tick.js
// timerFunc simply puts the flushCallbacks function into the browser's asynchronous task queue
let timerFunc
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    / / first Promise
    p.then(flushCallbacks)
    /** * In UIWebViews in question, promise.then does not break completely, but it may fall into a weird state, * in which the callback is pushed to the microtask queue, but the queue is not refreshed until the browser needs to do something else, such as handle a timer. * Therefore, we can "force" the microtask queue to refresh by adding an empty timer. * /
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = trueelse if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Then use MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterDatatrue
  })
  timerFunc = () = > {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = trueelse if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  // Then setImmediate, the macro task
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  / / the last setTimeout
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}Copy the code

flushCallbacks

// src/core/util/next-tick.js
Execute every function in the callbacks array (such as flushSchedulerQueue, the callback passed by the user when calling nextTick). * /
function flushCallbacks ({
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
Copy the code

Both the global API vue. nextTick and the instance method vm.$nextTick end up calling the nextTick method in next-tick.js.

A link to the

Vue (V2.6.14) source code detoxification (pre) : handwritten a simple version of Vue

Vue (V2.6.14) source code detoxification (a) : preparation

Vue (V2.6.14) source code detoxification (two) : initialization and mount

Vue (V2.6.14) source code detoxification (three) : response type principle

Vue (V2.6.14) source code detoxification (four) : update strategy

Vue (v2.6.14) source code detoxification (five) : render and VNode

Vue (v2.6.14) source code: Update and patch (to be continued)

Vue (v2.6.14) source code detoxification (seven) : template compilation (to be continued)

If you think it’s ok, give it a thumbs up!! You can also visit my personal blog at www.mingme.net/