preface

As we all know, one of the biggest features of Vue is data-driven views, i.e. data changes, view updates, which can be abstracted as:

In the above formula: the state state is the input, the page UI output, once the state input changes, the page output also changes. We call this feature data-driven views.

So here’s the question:

1. How does Vue know that state changes?

How is the render function derived?

What are the details of the render function that renders the page from the input data?

What’s the difference between performing the render function and manipulating the DOM directly?

5,…

Hope the following answers your questions!

Change detection

The data was hijacked

There are several ways in JavaScript to know that data has changed:

1. Manual invocation: Centralized data management. Every time data needs to be changed, specific functions are used to change data, such as setState in React and setData in applets

2. Data Agent: JavaScript provides object.defineProperty (ES5) and Proxy (ES6) methods to intercept data reads and writes. You can use this feature to override getters and setters of data to include data changes. Realize fine-grained understanding of data changes.

Vue uses the second method to detect data:

  • Vue2.x  => Object.defineProperty  

  • Vue3.x => Proxy

Note: Object.defineProperty is unable to detect additions and subtractions of Object attributes and array changes due to its own defects, so ES6 uses Proxy to solve these problems. This.$set, Vue. Set, override array methods to implement manually triggered updates.

Depend on the collection

In the last chapter, we took the first step: making data observable. Once it’s observable, we know when the data has changed, so when the data has changed, we can notify the view to update it.

So here comes the question:

The view is so big, who should we tell to change it? It is not reasonable to update the entire view when a single data change occurs.

The approach used by Vue is to use a collector for each piece of data to record who is currently using the data, and to notify all consumers in the collector of updates when the data changes.

In technical terms: rely on collect + publish subscribe

Here comes the question:

1. What is dependence?

2. When to collect dependencies?

3. When are dependency updates notified?

In fact, in Vue, the dependency is the instance of Watcher class. In the whole life cycle of Vue, there are three Watcher instances, which are:

1. Render Watcher: Render UI interface according to data, update view efficiently when data changes (render function generates new virtual DOM, patch process compares old and new virtual DOM, update view)

2. Compute properties Watcher: Performs complex logical operations that change data

3. Plain Watcher: Watch methods written manually by users

The Watcher class and the pseudo-code for the dependent collection are as follows:

1. When instantiating the Watcher class, its constructor is executed first;

The constructor calls the this.get() instance method;

3. In the get() method, we first assign the instance itself to a global unique object, window.target, with window.target = this

Let value = this.getter.call(vm, vm); let value = this.getter.call(vm, vm);

In the getter, call dep.depend() to collect the dependency, get the window.target value from dep.depend() and store it in the dependency array DEP, and release window.target at the end of get().

6. When the data changes, the setter of the data is triggered, in which the dep.notify() method is called. In dep.notify(), the update() method of the dependency is executed by iterating through all dependencies. The update() instance method in the Watcher class updates the view by calling the update callback for data changes in the update() method.

The life cycle

review

As we can see from the figure, the life cycle of a Vue instance can be roughly divided into four phases:

1. Initialization stage: initialize some attributes, events and response data for Vue instance;

2. Template compilation stage: compile the template into a rendering function;

3. Mount stage: mount the instance to the specified DOM, that is, render the template to the real DOM;

4. Destruction stage: delete the instance itself from the parent component, and cancel the dependency trace and event listener;

The initial stage

1. New Vue() is the _init method on the Vue prototype chain

InitLifeCycle: attach attributes like $root, $parent, $children to the instance to access all levels of component instances

InitEvents: initializes the parent component in the template to listen for events triggered by the child component using a V-ON or @ register.

4. InitState: Initialize props, methods, data, computed, watch and other data, in which data hijacking and dependency collection are implemented, and the properties in these properties are also proxy to the instance, so that the data in the properties can be accessed directly through this.xx

5. CallHook: Execute the corresponding life cycle function passed in by the user

Template compilation stage

In fact, the compilation stage is to compile the template into the render function, after all, handwriting render function is a certain difficulty, is also very prone to error, through the template to enhance the performance of the render function, but also greatly simplifies the template writing difficulty.

Mount the stage

In the mount stage, the EL node is replaced by the Vue instance, and the template content is output to the corresponding mount point. At the same time, data monitoring of Vue is enabled to monitor data changes. When data changes, view update is triggered.

Destruction of phase

The main work of the destruction phase is to remove the current Vue instance from its parent instance, remove all dependency tracing on the current instance, and remove all event listeners on the instance.

Virtual DOM

What is the virtual DOM

The virtual DOM is essentially a JS object representing a DOM node, as shown below:

Virtual DOM in Vue

The role of the virtual DOM in Vue

We compile the template into VNode and cache it before rendering the view.

When the data changes and the page needs to be rendered again, we compare the VNode generated after the data changes with the VNode cached before to find out the difference (JS object comparison and real DOM comparison, JS object comparison is definitely faster). Then the actual DOM nodes corresponding to the different VNodes are the nodes that need to be re-rendered.

Finally, a real DOM node is created based on the different VNodes and then inserted into the view to complete a view update.

Template compilation

The flow chart

Template compilation to view update process:

1. Template parsing stage: parse a bunch of template strings into abstract syntax tree AST by means of regular;

2. Optimization stage: Traverse the AST to find out the static nodes and mark them. Since the static nodes will not be updated in view update, they can be skipped directly, thus reducing the range of patch function comparison and improving efficiency.

3. Code generation stage: Transform AST into rendering function, which is used to generate virtual DOM representing template;

What is the render function

The Render function is a tool for building the virtual DOM, which is executed to generate a virtual DOM object that represents the current DOM structure

Templates are parsed to AST

AbstractSyntaxTree (AST) is an abstract representation of the syntactic structure of source code. It represents the syntactic structure of a programming language as a tree, with each node in the tree representing a structure in the source code. The AST objects generated by Vue parsing templates are shown below.

Template parsing:

Template nature actually is a string, set a cursor currently being parsed character position, by means of corresponding with regular matching string, constantly find out the current character position corresponding to the meaning of (for example, tags, attributes, etc.), add all kinds of the meaning of the characters to the corresponding attributes, and move back to the current cursor

Repeat until you have traversed all the strings, and you end up with an AST object that represents the current template information.

The optimization phase

To improve Vue’s template compilation efficiency, Vue introduced the ability to mark static nodes, such as:

In this template, the dynamic part of the SPAN tag package will be updated in real time, but the content of ul and LI tags is fixed and will not change with data update.

Therefore, ul and LI nodes can be marked as static nodes during template compilation, so that Vue will be

  • There is no need to create nodes for it each time it is rerendered
  • You can also skip this part entirely during the virtual DOM alignment process

Code generation phase

The code generation phase is the process of converting an AST into a render function, as shown in the render function generated by the following code:

*c, * v, and _m…… This is the method used to generate the virtual DOM

View update

process

1. For the first mount, use the virtual DOM generated by render function to directly generate the corresponding real DOM and add it to the mount node

2. Data changes, triggering data setter functions, and the current data manager Dep notifies it of dependency updates

3. Rendering relies on triggering the render function to regenerate the new virtual DOM according to the latest data. The patch function is used to compare the differences of the virtual DOM before and after

The DIFF algorithm

The biggest purpose of VNode is to generate virtual DOM nodes corresponding to the real DOM before and after data changes. Then, you can compare the old and new VNodes to find out the differences, and then update the DOM nodes with the differences, and finally achieve the purpose of updating the view with the least operation on the real DOM.

The process of comparing the old and new vNodes and finding the differences is known as the DOM-diff process.

When virtual DOM nodes are compared, they will first compare whether the key values of the current two Vnode nodes are the same. Node comparison and reuse will be carried out only if the conditions are met. This is why v-for needs to specify a unique key value to prevent incorrect reuse of the original node, resulting in errors in updating. Here is how to check if it is the same node:

There are four cases of updating the child node updateChildren:

1. Create child nodes

If a child node in newChildren cannot find the same child node in oldChildren, it means that the child node in newChildren does not exist before and needs to be added this time, so the child node is created.

2. Delete child nodes

If there are still unprocessed child nodes in oldChildren after completing the loop of each child node in newChildren, it means that these unprocessed child nodes need to be discarded, then delete these nodes.

3. Move the child node

If a child node in newChildren finds the same child node in oldChildren but in a different position, it indicates that the change needs to adjust the position of the child node, then the position of the child node in newChildren is taken as the baseline. Adjust the position of the node in oldChildren to be the same as in newChildren.

4. Update nodes

If one of the children in newChildren finds the same child in oldChildren in the same location, then the node in oldChildren is updated to be the same as the node in newChildren.

The easiest way to compare child nodes is a two-layer loop to find the same node for comparison, as shown in the pseudo-code below:

However, the complexity of this operation increases exponentially as the number of nodes increases. To optimize this, Vue does the following special processing before the loop:

1, thenewChildrenAnd the first child of all unprocessed child nodes in the arrayoldChildrenCompare the first child node of all unprocessed child nodes in the array. If they are the same, that’s great. Go straight to the update node operation described in the previous article and since the new node is in the same position as the old node, no node movement is required. If not, it doesn’t matter, try the next three.

2, thenewChildrenAnd the last child of all unprocessed child nodes in the arrayoldChildrenCompare the last child node of all the unprocessed child nodes in the array. If they are the same, the operation of updating the node will be directly entered. Since the position of the new node is the same as that of the old node, there is no need to move the node. If not, keep trying.

3, thenewChildrenAnd the last child of all unprocessed child nodes in the arrayoldChildrenCompare the first child nodes of all unprocessed child nodes in the array. If they are the same, then directly enter the operation of updating the nodeoldChildrenThe node in the array moves to andnewChildrenThe same position of the nodes in the array;

4, thenewChildrenAnd the first child of all unprocessed child nodes in the arrayoldChildrenCompare the last child node of all unprocessed child nodes in the array, if the same, then directly enter the operation of updating the node, after the updateoldChildrenThe node in the array moves to andnewChildrenThe same position of the nodes in the array;

If none of the four optimizations is hit, the loop comparison is performed.

conclusion

Reference Documents:

Vue source code series: vue-js.com/learn-vue/ wall crack recommended, many parts of this article drew on the series of articles

Vue official document: cn.vuejs.org/v2/guide/