After analyzing the entire initialization process of the VUE Demo in the previous seven chapters, this article begins by analyzing the vUE process when the data (model) changes.

We analyze the UPDATE phase of Vue by clicking on dom to trigger plus to execute this.info.age++.

proxyGetter && proxySetter

IninState ->initData->proxy(VM, “_data”, key)

function proxy(target, sourceKey, key) {
  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

Where this is the Vue instance and sourceKey is _data, that is, the key in the _data object is returned. IninState ->initData->observe->walk->defineReactive? ReactiveGetter in 1:

reactiveGetter && reactiveSetter

Object.defineProperty(obj, key, {
  enumerable: true.configurable: true.get: function reactiveGetter() {
    var 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) {
    var value = getter ? getter.call(obj) : val;

    if(newVal === value || (newVal ! == newVal && value ! == value)) {return;
    }

    if (true && customSetter) {
      customSetter();
    }

    if(getter && ! setter) {return;
    }

    if (setter) {
      setter.call(obj, newVal);
    } else{ val = newVal; } childOb = ! shallow && observe(newVal); dep.notify(); }});Copy the code

Dep.target (value) does not exist because watcher update is not being performed.

Then read this.info.age, which directly triggers reactiveGetter to return value.

At the end of the read phase, reactiveSetter is triggered to set the new value of this.info.age.

childOb = ! shallow && observe(newVal); dep.notify();Copy the code

After listening for the new value, the corresponding subscriber center Dep is notified of the variable.

Dep.prototype.notify

Dep.prototype.notify = function notify() {
  var subs = this.subs.slice();

  if (true && !config.async) {
    subs.sort(function (a, b) {
      return a.id - b.id;
    });
  }

  for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};Copy the code

Where subs is the subscription list of variables. In this case, the variable this.info.age has three subscriber watcher, one from watch Watcher, one from render watcher, and one from calculate Watcher.

In Notify, if synchronization is configured, the subscription list subs are sorted by ID size. The update method for each item (watcher) is then triggered in turn.

Watcher.prototype.update

Watcher.prototype.update = function update() {
  if (this.lazy) {
    this.dirty = true; // Set the identifier bit for calculating watcher
  } else if (this.sync) {
    this.run(); // If synchronization is set, it is executed immediately
  } else {
    queueWatcher(this); // Take the current instance as an argument}};Copy the code

Watcher.prototype.update focuses on some processing of the configuration and then passes the current Watcher to queueWatcher.

Watcher is computed as lazy (that is, this.lazy is true), so queueWatcher is not executed, but the identifier bit this.dirty is set to true. Attribute values in computedGetter behind, it is calculated through Watcher. Prototype. The evaluate to recalculate the value rather than directly from the cache values, and will also identify an enclosing dirty set to false, If the watcher update is not triggered by any other value update in the expression corresponding to the calculated attribute, the value can be directly fetched.

queueWatcher

function queueWatcher(watcher) {
  var id = watcher.id;

  if (has[id] == null) {
    has[id] = true; // Prevent multiple pushes from the same watcher at the same time

    if(! flushing) { queue.push(watcher);// Whether to flushSchedulerQueue is executed
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      var 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; // Prevent multiple nextTick, that is, prevent multiple registration of new microtask queues

      if (true && !config.async) {
        flushSchedulerQueue();
        return;
      }

      nextTick(flushSchedulerQueue); Pass the flushSchedulerQueue function into nextTick}}}Copy the code
  • hasIs an object used to filter the samewatcherMultiple calls at the same timeupdate
  • queueIs an array that holds data waiting to be updatedwatcherThe queue
  • flushingIs a Boolean value that identifies whether the queue is performing updates untilqueueReset to after the execution is completefalse
  • flushSchedulerQueueThe execution function for the update queue
  • waitingIs a Boolean value used to indicate that theflushSchedulerQueueRegister to the task queue for the next microloop untilqueueReset to after the execution is completefalse

In queueWatcher, we push the current Watcher instance into a queue, and pass the flushSchedulerQueue into the nextTick method.

nextTick

function nextTick(cb, ctx) {
  var _resolve;
  // Callbacks save the queue of asynchronously executed tasks
  callbacks.push(function () {
    if (cb) {
      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(function (resolve) { _resolve = resolve; }); }}Copy the code

In nextTick, the asynchronously executed task queue CB is uniformly collected into the Callbacks array (flushSchedulerQueue is one item) and timerFunc is executed.

TimerFunc executes flushCallbacks asynchronously. Resolve (). Then >MutationObserver>setImmediate>setTimeout ().

NextTick (flushSchedulerQueue) : async wait; dep.prototype. notify: update the next watcher to queue.

In flushCallbacks, the method iterates through the callbacks and executes them.

Why would you do that

If there is no asynchronous queue, update->patch will be triggered every time the data changes to compare vNodes to update the DOM. If there is a large number of changes at the same time (watcher is triggered many times), the view will be updated frequently and repeatedly. With this queue, watcher repeatability can be determined on the current stack and repeated updates can be filtered out. Performance is greatly improved by ensuring that the action of updating the view to manipulate the DOM is called in the next tick event loop after the current stack completes execution.

flushSchedulerQueue

function flushSchedulerQueue() {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  queue.sort(function (a, b) {
    return a.id - b.id;
  });

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];

    if (watcher.before) {
      watcher.before();
    }

    id = watcher.id;
    has[id] = null;
    watcher.run(); // in dev build, check and stop circular updates.

    if (true&& 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; }}}// keep copies of post queues before resetting state

  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();
  resetSchedulerState(); // call component updated and activated hooks

  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue); // devtool hook

  if (devtools && config.devtools) {
    devtools.emit('flush'); }}Copy the code

For this example, there are currently two watchers in the queue: Watch watcher, render watcher. FlushSchedulerQueue sorts the queue first:

  • Component updates are parent to child (because the parent component is created before the child component), sowatcherThe creation and execution order of the line should also be the parent after the child
  • User definedwatcherShould be inRender the watcherBefore executing (because of user customizationwatcherThe creation of theRender the watcherBefore)
  • If a component is in the parent component’swatcherDuring execution is destroyed, then the subcomponent’swatcherCan be skipped (this.activeLogo).

Then execute the watcher. Before for each watcher in the queue to trigger the lifecycle beforeUpdate hook. Then execute watcher.run(), as analyzed below.

After execution, call resetSchedulerState to reset the state, call callActivatedHooks to change the component to the Activated state and trigger the lifecycle Activated hook, Call callUpdatedHooks to trigger the lifecycle Update hook, which triggers the tool’s Flush event, and the process ends.

Watcher.prototype.run

Watcher.prototype.run = function run() {
  if (this.active) {
    var value = this.get();

    if(value ! = =this.value || isObject(value) || this.deep) {
      // set new value
      var oldValue = this.value;
      this.value = value;

      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, 'callback for watcher "' + this.expression + '"'); }}else {
        this.cb.call(this.vm, value, oldValue); }}}};Copy the code

This. Active is used to identify whether the watcher has been uninstalled. The Watcher. Prototype. Set to false in the teardown (unloaded).

Watcher.prototype.run executes this.get to get value assigned to this.value. Then update watcher’s value. If this.user is true and represents a user-defined watch, execute cb, the user-defined callback method, and watch Watcher will trigger the callback.

Watcher.prototype.get

Watcher.prototype.get = function get() {
  pushTarget(this);
  var value;
  var vm = this.vm;

  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, 'getter for watcher "' + this.expression + '"');
    } else {
      throwe; }}finally {
    if (this.deep) {
      traverse(value);
    }

    popTarget();
    this.cleanupDeps();
  }

  return value;
};
Copy the code

Watcher. Prototype. Mainly in the get through this. Getter get value.

Dep. Target = Dep. Target = Dep. Target = Dep.

If deep is set to true, traverse recursively executes each child property of the _traverse read object to add the watcher subscription to them.

  • Where the getter for watch Watcher is:

    / /...
    return function (obj) {
      for (var i = 0; i < segments.length; i++) {
        if(! obj) {return;
        }
    
        obj = obj[segments[i]];
      }
    
      return obj;
    };
    / /...
    Copy the code

    Obj [segments[I]] [segments] [segments]] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] [segments] So it’s also added to info.__ob__.

  • Where the getter for rendering watcher is:

    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
    Copy the code

    Vm. _render gets vNodes based on the render function. During render, every variable read will trigger its corresponding proxyGetter->reactiveGetter to get the latest value and the render Watcher will subscribe to the corresponding variable’s subscription list. Once you have the VNode, then execute vm._update to update the view, which is analyzed in the next chapter.

Finally, the cleanupDeps method is executed to delete invalid subscription subs by comparing the old and new depIds. Finally, value is returned to the flushSchedulerQueue method.

The summary of this chapter

  1. This chapter starts with a data update and analyzes the logic associated with adding subscriptions to data and triggering subscriptions.
  2. There are three of themwatcherAre:watch watcher.Render the watcher.Calculate the watcherFor differentwatcherIt’s done differently.
  3. Triggering subscriptions involves asynchronous queue optimization that optimizes unnecessary view updates, greatly improving performance.