Scheduling Principle of VUE source Code Parsing (Responsive Principle)

See my previous article for a better understanding: the compilation process of vue source parsing – including two modes (and vue-loader function)

Outline of the directory

  1. Test file:.html file
  2. Test action: Click “Click me” to trigger the QQQ function
  3. Summary of scheduling process
  4. Let’s talk about vUE’s bidirectional binding V-Model

Test file:.html file

  • CDN introduces an uncompressed version of VUE, which is used directly within script tags
    <! DOCTYPEhtml>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    </head>
    <body>
    <div id="app">
      {{aa}} --- 1
      <div @click="qqq">click me</div>
      {{C_aa}}
    </div>
    <script type="module">
      debugger
      new Vue({
        el: '#app'.data: {
          aa: 123
        },
        watch: {
          aa (nval, oval) {
            console.log(nval, oval)
          }
        },
        computed: {
          C_aa () {
            return this.aa + 100}},methods: {
          qqq () {
            debugger
            this.aa = this.aa + 1}}})</script>
    </body>
    </html>
    Copy the code

Test action: Click “Click me” to trigger the QQQ function

Note: only cut the main line code, and slightly deleted, in order to better focus on the main line, the main line figure out, have the power to understand the branch. Debug mode: Debugger step by step

Aa = this.aa + 1 vue (vue) = this.aa + 1 vue (vue)

Here are a few questions to think about in advance:

  1. If a user changes multiple data values in a single synchronization operation, does VUE trigger one update or multiple updates?
  2. Watch: {.. } is the callback function executed before or after update?
    1. watch: {.. } will update be triggered if the data callback is modified?

Start debugging, executethis.aa = this.aa + 1

  1. The first step is to get the value this.aa. Aa’s get listener is triggered because it needs to be evaluated

    • In vUE, data is listened for (recursively for deep objects and traversal for arrays). Object.defineproperty is used to set get and set listeners. Setting this.aa=xx triggers the set function

    • See the comments for the following steps to perform get (the following DEP section uses the publish subscribe model)

      /** * A dep is an observable that can have multiple * directives subscribing to it. */
      var Dep = function Dep () {
        this.id = uid++;
        this.subs = []; // Subscriber queue
      };
      /** * Define a reactive property on an Object. */
      function defineReactive$$1 (obj, key, val, customSetter, shallow) {
        var dep = new Dep(); // Bind a DEP object to each data (the deP constructor structure is shown above)
    
        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
          return
        }
    
        // cater for pre-defined getter/setters
        var getter = property && property.get;
        var setter = property && property.set;
        if((! getter || setter) &&arguments.length === 2) {
          val = obj[key];
        }
    
        varchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, {
          enumerable: true.configurable: true.get: function reactiveGetter () {
            var value = getter ? getter.call(obj) : val; // Data exists in the getter
            /* Collect dependencies for data (in VUE, each data is bound to an object called deP, which is assigned a unique ID. If there are dependencies, they will be placed in the subscriber queue of this.subs in the deP corresponding to data. Aa has three required functions that rely on a watch, a computed, and a page rendering "function () {vm._update(vm._render(), hydrating); } "* /
            if (Dep.target) { 
              dep.depend(); // Collect dependencies for data
              if (childOb) { // Handle child recursively
                childOb.dep.depend();
                if (Array.isArray(value)) { dependArray(value); }}}return value / / get the value
          },
          set: function reactiveSetter (newVal) {
            var value = getter ? getter.call(obj) : val; // Data exists in the getter
            // The new value is not changed
            if(newVal === value || (newVal ! == newVal && value ! == value)) {return
            }
            // #7981: for accessor properties without setter
            if(getter && ! setter) {return }
            if (setter) { // Execute only when vUE is initialized
              setter.call(obj, newVal);
            } else {
              val = newVal; // Save a new value} childOb = ! shallow && observe(newVal);// Handle child recursively
            /* Message push notifies the subscriber queue this.subs. It's actually going to process the subscriber queue, put it in the global queue, and what actually happens in the end is the queue queue, it's going to filter out computed because it's not asynchronous, and the result is the return value of the function. When the model layer is rendered, the function is run to return the value (currently aa's subscriber queue this.subs contains: 1 watch, 1 computed, 1 required function for page rendering "function () {vm._update(vm._render(), hydrating); * /} ")dep.notify(); }}); }Copy the code
  2. Second, change the value of this.aa. The set listener is triggered

    Executing the set listener will eventually trigger the notification push dep.notify().

  3. Dep.notify () scheduling start

    A message push notifies the subscriber queue this.subs. The subscriber queue is actually processed and placed in the global queue, and the queue is actually executed (currently aa’s subscriber queue this.subs contains: 1 watch, 1 computed, 1 required function for page rendering “function () {vm._update(vm._render(), hydrating); }”) (filters out computed because it is not asynchronous, and the result is the return value of the function. When the model layer values are rendered, computed functions are run to return values.)

    1. “Function () {vm._update(vm._render(), hydrating); }”}”}”}”}”}”}”}”}”}”}”}”}”}

    2. Detail 1: Computed by the user is not asynchronous, and the result is the return value of the function. When the model layer values are rendered, computed corresponding functions are run to get the value returned (the code below is not shown for now)

      • The scheduler will only set this.dirty = true. Changing computed to dirty means that it needs to be updated.
    3. Detail 2: Asynchronous events (such as user-written watches) are placed in a global queue, the last of which is the key render function vm._update(vm._render()).

    4. Detail 3: When to queue?

      • NextTick (flushSchedulerQueue)
        • The nextTick principle is a microtask. When the synchronization task is completed, the flushSchedulerQueue is executed and the run queue is finally executed.
        • Benefits: A user’s operation may change multiple or multiple data values. Instead of updating the page for each change, all changes in a synchronization task can be collected and placed in the queue. Then, after the synchronization task is finished, the callback function in the microtask nextTick can be executed to execute the Run queue.
    5. Detail 4: Watch: {.. } will update be triggered if the data callback is modified?

      • There won’t be multiple vm._update(vm._render())
      • We’re going to use the global variable flushing to make sure that there’s only one update for a synchronization task

    Scheduling process:

    /* Some of the non-mainline code has been deleted, in order to better focus on the mainline, mainline figure out, have the power to understand the branch */ 
    
    Dep.prototype.notify = function notify () {
        var subs = this.subs.slice();
        for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};/**
    * Subscriber interface. Will be called when a dependency changes.
    */
    Watcher.prototype.update = function update () {
        /* istanbul ignore else */
        if (this.lazy) { // Computed by the user goes in here, not asynchronously, and the result is the return value of the function. When the model layer values render, it will run the function and get the return value
            this.dirty = true; // Changing the corresponding computed to dirty means that an update is needed
        } else if (this.sync) { // A synchronization task. For example, this.aa=xx triggers the aa watch callback, which in turn changes this.bb= XXX non-asynchronously
            this.run();
        } else {
            queueWatcher(this); // Watch: {aa () {}} go inside}};/** * Push a watcher into the watcher queue. Push watcher to the Watcher queue. * Jobs with duplicate IDs will be skipped unless it's pushed when the queue is being flushed. Events with duplicate ids are skipped unless pushed when the queue is refreshed. * /
    function queueWatcher (watcher) {
        var id = watcher.id;
        if (has[id] == null) {
          has[id] = true;
          if(! flushing) {// The global variable flushing ensures that a synchronization task does not have more than one vm._update(vm._render()), but only one update
            queue.push(watcher); // Add watcher to queue
          } else { // Avoid duplicates
            // 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;
            The nextTick principle is a micro task. When the synchronization task is completed, all watcher units are added to the queue, and the run queue is executed in the flushSchedulerQueue.
            nextTick(flushSchedulerQueue); // Watch: {aa () {}} go inside. Is asynchronous}}}/**
    * Flush both queues and run the watchers.
    */
    function flushSchedulerQueue () {
        flushing = true;
        var watcher, id;
        // Queue is a global watcher list containing all watcher users in the current synchronization task
        Function () {vm._update(vm._render(), hydrating); function () {vm._render(); }"
        for (index = 0; index < queue.length; index++) {
          watcher = queue[index];
          if (watcher.before) {
            watcher.before(); // execute vm._update(vm._render(), hydrating) before beforeUpdate callHook(vm, 'beforeUpdate') here;
          }
          id = watcher.id;
          has[id] = null;
          watcher.run(); // Watch: {aa () {}} is executed on this line. "Function () {vm._update(vm._render(), hydrating); } ")}}Copy the code

    watcher.run();

    • Watcher does it all here, like
      • 1. Watch: {aa () {}}
      • “Function () {vm._update(vm._render(), hydrating); } “)
        • Ensures execution at the end of a synchronization task because multiple updates are avoided
    /**
    * Scheduler job interface. Will be called by the scheduler.
    */
    Watcher.prototype.run = function run () {
        if (this.active) {
          Watch: {aa(newVal, oldVal) {}} newVal (this.get()) = this.get(); "Function () {vm._update(vm._render(), hydrating); }") is executed here. * /
          var value = this.get(); 
          if( value ! = =this.value ||
            // Deep watchers and watchers on Object/Arrays should fire even
            // when the value is the same, because the value may
            // have mutated.
            isObject(value) ||
            this.deep
          ) {
            // set new value
            var oldValue = this.value;
            this.value = value;
            if (this.user) {
              var info = "callback for watcher \"" + (this.expression) + "\" ";
              // The developer writes watch: {aa () {}} to listen on this line
              invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info); 
            } else {
              this.cb.call(this.vm, value, oldValue); }}}};/**
    * Evaluate the getter, and re-collect dependencies.
    */
    Watcher.prototype.get = function get () {
        pushTarget(this);
        var value;
        var vm = this.vm;
        try {
          "Function () {vm._update(vm._render(), hydrating); }") is executed here. This. Getter saves vm._update(vm._render(), hydrating)
          value = this.getter.call(vm, vm);
        } catch (e) {
          if (this.user) {
            handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\" "));
          } else {
            throw e
          }
        } finally {
          // "touch" every property so they are all tracked as
          // dependencies for deep watching
          if (this.deep) {
            traverse(value);
          }
          popTarget();
          this.cleanupDeps();
        }
        return value
    };
    // The developer writes watch: {aa () {}} to listen on this line
    function invokeWithErrorHandling (handler, context, args, vm, info) {
        var res;
        try {
          // The developer writes watch: {aa () {}} to listen on this line
          res = args ? handler.apply(context, args) : handler.call(context);
          if(res && ! res._isVue && isPromise(res) && ! res._handled) { res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
            // avoid catch triggering multiple times when nested calls
            res._handled = true; }}catch (e) {
          handleError(e, vm, info);
        }
        return res
    }
    Copy the code

    nextTick()

    • The nextTick principle is a microtask that uses nextTick functions stored in the global Callbacks
    function nextTick (cb, ctx) {
        var _resolve;
        callbacks.push(function () { // Callbacks are global, storing functions that use nextTick
          if (cb) {
            try {
              cb.call(ctx);
            } catch (e) {
              handleError(e, ctx, 'nextTick'); }}else if(_resolve) { _resolve(ctx); }});if(! pending) { pending =true;
          timerFunc(); // The nextTick principle is a microtask that uses nextTick functions stored in callbacks
        }
        if(! cb &&typeof Promise! = ='undefined') {
          return new Promise(function (resolve) { _resolve = resolve; }}})var p = Promise.resolve(); Micro / / task
    timerFunc = function () { Micro / / task
      p.then(flushCallbacks);
      if (isIOS) { setTimeout(noop); }};function flushCallbacks () {
        pending = false;
        var copies = callbacks.slice(0); // Callbacks are global, storing functions that use nextTick
        callbacks.length = 0;
        for (var i = 0; i < copies.length; i++) {
          copies[i](); / / implement callbacks}}Copy the code
  4. The user’s watch:{… } “function () {vm._update(vm._render(), hydrating); }”

    • Vue update(vm._render(), hydrating), vue update(vm._render(), hydrating), vue update(vm._render(), hydrating), vue update(vm._render(), hydrating)

Summary of scheduling process

The scenario is: modify the data in data (let’s say change this.aa = 123), and update the corresponding position on the page

Process summary:

  1. Trigger aa’s set listener
  2. Handles aa’s subscriber queue subs (Subscriber queue includes: computed, watch, key render functions)
    • Change the value of computed to this.dirty = true, and the value of computed in the model layer will be recalculated the next time the vNode is generated. Otherwise, caching is used
  3. The callbacks in watch are placed in the global object Queue. It is controlled by nextTick. During the synchronization task, it is added to the queue. After the synchronization task is complete, the run queue starts
    • Function () {vm._update(vm._render(), hydrating); function () {vm._render(); } “)
      • We have the global variable flushing control, which ensures that a synchronization task only executes the key render function once
    • The nextTick principle is microtasks,
  4. Vue update(vm._render(), hydrating), vue update(vm._render(), hydrating), vue update(vm._render(), hydrating), vue update(vm._render(), hydrating)

Let’s talk about vUE’s bidirectional binding V-Model

Is actually a syntactic sugar (syntactic sugar can be understood as shorthand, the second line below is what it actually looks like)

<input v-model='abc' />/ / syntactic sugar<input :value='abc' @input='abc = $event.target.value' />Note: value is the value of the form control. Submit with the form as a name/value pairCopy the code

The process is: DOM Listeners -> Model -> Data Bindings -> render to page

  • DOM Listeners such as Input events and Select events (so V-Models only support form elements)
  • Model is the Model layer (data layer). With events, modify this. ABC = $event.target.value. This then fires the set function of this.aa, which then fires the key render function vm._update(vm._render(), hydrating). Finally render to the page

Code word is not easy, praise encouragement