Vue.js responsive system principle

personal bolg address

Global overview

Overview of the internal operating mechanism of Vue running:

Initialize and mount

after  new Vue(). Vue will call the  _init function to initialize, which is the  init process here, it will initialize the life cycle, events,  props,  methods,  data,  computed and  watch, etc. The most important thing is to  set the setter and  getter functions through Object.defineProperty  to achieve "responsiveness" and "dependency collection", which will be described in detail later, as long as there is an impression here.

After initialization, calling $mount will mount the component. If it is compiled at runtime, that is, if there is no  render function but there is a  template, the "compile" step is required.
Because the compilation includes build-time compilation and runtime compilation, the purpose is to convert the template into a fried rice cake render function, so if the runtime checks that the template exists but there is no render function, the template will be compiled into a render function.

compile

compile compilation can be divided into three stages: parse, optimize and generate, and finally need to get the render function

 

parse (parse)

parse will parse the instructions, class, style and other data in the template template using regular methods to form AST.

optimize (optimize)

The main function of optimize is to mark static static nodes, which is an optimization of Vue in the compilation process. Later, when update updates the interface, there will be a patch process, and the diff algorithm will skip the static nodes directly, thus reducing the comparison. process to optimize the performance of the patch.

generate (generate)

generate is the process of converting the AST into a render function string, and the result is a render string and a staticRenderFns string.
After going through the three stages of parse, optimize and generate, the render function required to render VNode will exist in the component.

Responsive

Next is the responsive core part of Vue.js.
The getter and setter here have been introduced before. They are bound by Object.defineProperty during init, which makes the getter function executed when the set object is read, and will be executed when the set object is assigned. Execute the setter function.
When the render function is rendered, because the value of the required object is read, the getter function will be triggered to perform "dependency collection". The purpose of "dependency collection" is to store the watcher object in the subscriber in the current closure Dep's subs. Form such a relationship as shown below.

 

When the value of the object is modified, the corresponding setter will be triggered, and the setter will notify each Watcher in the Dep obtained by "dependency collection" before, telling them that their value has changed and the view needs to be re-rendered. At this time, these Watchers will start to call update to update the view. Of course, there is also a patch process and the strategy of using queues to update asynchronously, which we will talk about later.

Virtual DOM

We know that the render function will be converted into a VNode node. Virtual DOM is actually a tree based on JavaScript objects (VNode nodes), and uses object attributes to describe nodes. In fact, it is just an abstraction of the real DOM. Finally, this tree can be mapped to the real environment through a series of operations. Since Virtual DOM is based on JavaScript objects and does not depend on the real platform environment, it has cross-platform capabilities, such as browser platforms, Weex, Node and so on.
For example, the following example:

{
    tag: 'div',                  /* indicates that this is a div tag */ 
    children: [                  /* stores the child nodes of this tag */
        {
            tag: 'a',            /* indicates that this is an a tag */ 
            text: 'click me'     /* the content of the tag */
        }
    ]
}

After rendering, you can get

<div>
    <a>click me</a>
</div>

This is just a simple example, the actual node has more attributes to mark the node, such as isStatic (representing whether it is a static node), isComment (representing whether it is a comment node) and so on.

update view

 

As we mentioned earlier, when modifying an object value, the corresponding view will be modified through the process of setter -> Watcher -> update, so how to update the view in the end?

When the data changes, a new VNode node can be obtained by executing the render function. If we want to get a new view, the easiest and rude way is to directly parse the new VNode node, and then use innerHTML to directly render it all into the real DOM . But in fact we only modified a small part of it, which seems a bit "wasteful".

So why can't we just fix the "changes"? This time we will introduce "patch". We will compare the new VNode and the old VNode together with the patch, and get their "difference" through the diff algorithm. Finally, we only need to modify the corresponding DOM of these "differences".

Fundamentals of Reactive Systems

Responsive system

Vue.js is an MVVM framework. The data model is just ordinary JavaScript objects, but when operating on these objects, it can affect the corresponding views. Its core implementation is "responsive system". Although we will not directly modify the "responsive system" when using Vue.js for development, understanding its implementation will help avoid some common "pits", and it will also help you to meet some elusive problems. Dig into its principles to solve it.
Object.defineProperty
First, let's introduce Object.defineProperty, on which Vue.js implements the "responsive system".

The first is to use:

/*
    obj: target object
    prop: The property name of the target object to be manipulated
    descriptor: descriptor=>{
      enumerable: false, //Whether the properties of the object can be enumerated in the for...in loop and Object.keys()
      configurable: false, //Whether the properties of the object can be deleted, and whether other properties except writable properties can be modified.
      writable: false, // when true, the value can be changed by the assignment operator. Defaults to false.
      value: "static", //The value corresponding to this property. Can be any valid JavaScript value (number, object, function, etc.). Defaults to undefined.
      get : function(){ //A method that provides a getter for a property, or undefined if there is no getter. The return value of this method is used as the property value. Defaults to undefined.
        return this.value;
      },
      set : function(newValue){ //Method to provide setter, undefined if there is no setter. Assign the new value of the parameter to the property. Defaults to undefined.
        this.value = newValue;
      },
    }
    return value incoming object
*/
Object.defineProperty(obj, prop, descriptor)

// take a chestnut

// use __proto__ 
var obj = {};
 var descriptor = Object.create( null ); // no inherited properties 
// default no enumerable, no configurable, no writable 
descriptor.value = 'static' ;
Object.defineProperty(obj, 'key', descriptor);

// 显式
Object.defineProperty(obj, "key", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static"
});
// Example of adding a property and access descriptor to the object 
var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

To get familiar with Object.defineProperty you can go to the MDN documentation to review the examples.

Implement observer (observable)

Knowing Object.defineProperty, let's use it to make objects observable.
The content of this part has been initially introduced in the second subsection. In the init stage, initialization will be carried out, and the data will be "responsive"

 

For ease of understanding, we do not consider complex situations such as arrays, and only deal with objects.

First of all, we define a cb function, which is used to simulate view update. Calling it means updating the view. There can be some methods for updating the view inside.

 

function cb (val) {
     /* Rendering view */ 
    console.log( "The view is updated~" );
}

Then we define a defineReactive. This method uses Object.defineProperty to realize the "responsiveness" of the object. The input parameters are an obj (object to be bound), key (a property of obj), val (specific value) ). After defineReactive processing, the key property of our obj will trigger the reactiveGetter method when it is "read", and the reactiveSetter method will be triggered when the property is "written".

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true ,        /* property can be enumerable */ 
        configurable: true ,      /* property can be modified or deleted */ 
        get: function reactiveGetter () {
             return val;          /* will actually depend on the collection, which will be discussed in the next section * /
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

Of course this is not enough, we need to encapsulate another layer of observer on it. This function passes in a value (a "reactive" object), and processes each property of the object through defineReactive by traversing all properties.

function observer (value) {
     if (!value || ( typeof value !== 'object')) { /* Only consider objects, return non-objects */ 
        return ;
    }
    
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}

 

Finally, let's wrap a Vue with an observer! 

In the constructor of Vue, the data of options is processed. The data here must be familiar to everyone. It is the data attribute in the component when we usually write a Vue project (actually a function, which is treated as an object here for simple processing)

class Vue{
   /* Vue constructor class */
  constructor(options) {
    this._data = options.data;
    observer(this._data);
  }
}

 

In this way, as long as we new a Vue object, the data in data will be "responsive". If we do the following on the data property, it will trigger the cb method to update the view.

let o = new Vue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,world.";   /* The view is updated~ */

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325374753&siteId=291194637