Reading time about 16 ~ 22 min author: wang personal homepage: www.zhihu.com/people/wang…

One, foreword

There are many optimized open source frameworks based on VUE core and Compile on the market, which introduce vUE capabilities for non-Web scenarios. Therefore, learning costs are low and it is welcomed by the majority of developers. The following is a general list of what I have learned, and there are more excellent ones that are welcome to comment on

classification technology
Cross-platform native weex
Small program mpvue
Server side rendering Vue SSR
Small program multi – terminal unified framework uni-app

There are numerous frameworks that provide a VUe-like development experience, such as the applets framework wepy,

In other respects, The Github Daily chart, Vue, has 100 stars every day, which shows how popular it is, which is why people are rushing to offer Vue support in non-Web areas. Then the underlying architecture of Vue and its application become very important

Second, Vue underlying architecture

Understanding the underlying architecture of Vue is a major prerequisite for providing Vue capabilities to non-Web domains. Vue core is divided into three parts: core, Compiler and platform. The principles and capabilities of Vue are introduced below.

1, the core

Core is the soul of Vue. It is core that generates the platform view recursively through VNode and automatically diff updates the view when the data changes. It is also the vNode mechanism that makes core platform-independent, even though the function of core is UI rendering.

I’m going to illustrate Core in the following ways

  • mount
  • instruction
  • Vnode – to highlight
  • Component instance VMS and relationships between VMS
  • nextTick
  • Watcher – to highlight
  • Vnode Diff algorithm —- highlights
  • The core conclusion

1.1 mount

Append specific platform elements generated by vNode to known nodes. Taking the web platform as an example, we use vNode to generate the DOM from document.createElement and then append it to a node in the document tree. We’ll also talk about mounting components a lot later, which means executing the render component to generate a VNode and then iterating through the VNode to generate platform-specific elements. The root element of the component will be appended to the parent element.

1.2 instruction

Instructions are attributes with specific meanings in Vue. They can be divided into two categories: one is compile-time processing, which is reflected in the generated render function, such as V-if and V-for; the other is used at runtime, which is more for the operation of specific generated platform elements. For Web platforms, it is the operation of DOM

1.3 VNode——— Highlights

A VNode is a virtual node. It is a further abstraction (simplified version) of a specific platform element object. Each platform element corresponds to a VNode, and the specific platform element structure can be completely restored through the VNode structure. The following explains vNode in terms of the Web platform. For the Web, assume the following structure:

<div class="box" @click="onClick">------------------ corresponds to a Vnode<p class="content">Ha ha</p>------- corresponds to a Vnode<TestComps></TestComps>---------- User-defined components also correspond to a VNode<div></div>----------------------- corresponds to a Vnode</div>
Copy the code

Vue’s compile module generates a rendering function, which generates the corresponding VNode structure:

// I only list the key vNode information here
{
  tag:'div'.data: {attr: {},staticClass:'box'.on: {click:onClick}},
  children: [{tag:'p'.data: {attr: {},staticClass:'content'.on: {}},children: [{tag:' '.data: {},text:'ha ha'
    }]
  },{
    tag:'div'.data: {attr: {},on:{}},
  },{
    tag:'TestComps'.data: {attr: {},hook: {init:fn,           
            prepatch:fn,
            insert:fn,
           destroy:fn
        }
     },
  }]  
}
Copy the code

The outermost div corresponds to a VNode and contains three child VNodes. Note that the custom component also corresponds to a Vnode, but this vnode has component instances hanging from it

1.4 Component instance VMS and the relationship between VMS ——— Highlight

Component instances are Vue instance objects. Only custom components have them, not platform-specific elements. To understand Vue core, it is important to understand the following relationship. Now, let’s get a feel for it:

Given a template with the following structure, the vnode on the element represents the corresponding vnode name generated:

// New Vue template. The corresponding instance is vm1 <div vnode1> <p vnode2></p> <TestComps vnode3testAttr="hahha"
             @click="clicked"
             :username="username"
             :password="password"></TestComps>
</div>
Copy the code
<span vnode5></span> <p vnode6></p> </div>Copy the code
// The generated vnode relationship tree is vnode1={tag:'div',
  children:[vnode2,vnode3]
}
vnode3={
  tag:'TestComps',
  children:undefined,
  parent:undefined
}
vnode4={
  tag:'div', children:[vnode5,vnode6], parent:vnode3Copy the code
// The generated vm relationship tree is vm1={$data:{password: "123456",username: "aliarmo"}, // The component corresponds to state$props:{} // The data passed to the template when using the component$attrs: {},$children:[vm2],             
  $listeners: {}$options: {components: {} parent: undefined // propsData: undefined // Data passed to the template when using components _parentVnode: undefined}$parent: Undefiend // The parent instance of the current component$refs:{} // The dom reference contained in the current component$root:vm1 // Root component instance$vnode<TestComps></TestComps> _vnode:vnode1 // The vnode object corresponding to the root element of the current component template} vm2={$data:{} // The component corresponds to state$props:{password: "123456",username: "aliarmo"} // The data passed to the template when using the component$attrs: {testAttr:'hahha'},
  $children: [].$listeners:{click:fn}
  $options: {components: {} parent: vm1 // propsData: {password:"123456",username: "aliarmo"Parentvnode: vnode3}$parent:vm1 // Parent component instance of the current component$refs:{} // The dom reference contained in the current component$root:vm1 // Root component instance$vnode<TestComps></TestComps> _vnode:vnode4 // The vnode object corresponding to the root element of the current component template}Copy the code

1.5 nextTick

It allows us to do something in the next event loop, rather than in this loop, for asynchronous updates, based on microTask and MacroTask

Let’s look at this code:

new Promise(resolve= >{
  return 123
}).then(data= >{
  console.log('step2',data)
})
console.log('step1')
Copy the code

The result is that step1 is output, and then in step2, the promise of resolve is a microtask, and the synchronization code is macrotask

/ / in Vue
this.username='aliarmo' // Updates can be triggered
this.pwd='123'          // Can also trigger updates
Copy the code

Does changing both states trigger two updates? No, because the this.username callback that triggers the update is put into a microtask implemented via Promise or MessageChannel. Or the MacroTask implemented by setTimeout, to the next event loop.

1.6 Watcher——— Highlight

A component corresponds to a watcher. Create the watcher when the component is mounted. The state of the component, including data and props, is the object being observed. The action that the observed changes cause the observer to perform is vm._update(vm._render(), hydrating). The component rerenders to generate vNodes and patches.

Understand that this relationship is very important: observer contains definition of change to respond to a component corresponding to an observer all observed in corresponding components, the observer may be used for other components, an observer would correspond to more than one observer, when observed change, response to update notification to all observers.

State1 of component A has changed, which will cause watcher observing state1 to receive notification of the change, which will cause component A to re-render to generate A new VNode. During the process of component A’s new vNode and its old vNode patch, updateChildrenComponent, This causes the props of child component B to be reset to a new value, because child component B is observing the passing state1, and is notified to the corresponding watcher, resulting in an update for child component B

How the Watcher system was built:

  1. The component instance is created as an observer of data and props,
  2. The props are shallowly traversed, and the property descriptors get and set are reset. If an object of the props is an object, then the object is shallowly traversed by the parent component
  3. The Observer deeply traverses the data, redefining the attributes that data contains, namely defineReactive, and resetting the get and set attributes of the property descriptor
  4. In mountComponent, Wacther is new, the current Watcher instance is pushTarget, and the target Watcher is executedvm._update(vm._render(), hydrating)The render function causes the property’s get function to be called, and each property will have a DEP instance. At this point, the DEP instance is associated with the component’s corresponding Watcher to implement dependency collection and popTarget correlation.
  5. If there are child components, this will result in instantiation of the child components and repeat the steps above

State change response process:

  1. When state changes, deP calls the set function of the property descriptor, and the associated Watcher is notified to enter the nextTick task. The run function of the watcher instance containsvm._update(vm._render(), hydrating), execute the run function, resulting in vNode reconstruction, patch, diff, and UI update

How does a parent component state change cause child components to change as well?

When the parent component state is updated, the rendering function will be executed again to generate a newVnode. During oldVnode and newVnode patch, if the component vnode is encountered, the updateChildrenComponent will be executed. Update the props of the sub-component, because the sub-component is listening for changes in the props property, causing the sub-component to re-render

The parent component passes an object to the child component, and the child component changes the object props passed. How is the parent component updated to?

Premise: If the parent component passes an object to the child component, then the child component receives a reference to that object. This. Person in ParentComps and this. Person in SubComps refer to the same object

SubComps Vue.component('ParentComps',{data(){return {person:{username:'aliarmo', SubComps Vue.component('ParentComps',{data(){return {person:{username:'aliarmo', pwd:123 } } }, template:`<div>
      <p>{{person.username}}</p>
      <SubComps :person="person" />
    </div>`})Copy the code

In SubComps, update a property of the Person object, such as this.person.username=’wmy’.

In ParentComps, Vue will deeply recursively observe each property of the object. When executing ParentComps render for the first time, Watcher of ParentComps will be bound to SubComps, and the object passed in will not be observed. The first time SubComps render is performed, it is bound to the Watcher of SubComps, so when SubComps changes the value of this.person.username, both Watcher will be notified, resulting in an update. This is a good way to explain that mounting new properties from scratch on the props property object passed in does not trigger rendering, because the props property object passed in is being viewed in the parent component.

1.7 VNode Diff algorithm ——— Highlight

When the state of the component changes, the render function is re-executed to generate a new VNode, which is then compared to the old vNode to update the original view with minimal cost. The principle of the diff algorithm is to generate the newChildrenVnodes structure by moving, adding, deleting, and replacing the structure corresponding to oldChildrenVnodes. Each old element can only be reused once, and the final position of the old element depends on the current new vnode. The diff algorithm is explicitly passed in to the children of two SamevNodes, starting at the beginning and ending of both, and moving toward the middle until one of them reaches the middle.

PS: oldChildrenVnodes indicates a collection of oldChildrenVnodes. NewChildrenVnodes indicates a collection of newChildrenVnodes after state changes

SameVnode = sameVnode = sameVnode = sameVnode = sameVnode = sameVnode = sameVnode = sameVnode

  1. The key values of vNodes are the same, for example<Comps1 key="key1" />.<Comps2 key="key2" />, the key values are not the same,<Comps1 key="key1" />.<Comps2 key="key1" />Key values are the same,<div></div>.<p></p>, the key value of the two is undefined and the key value is the same, which is the premise of sameVnode.
  2. Vnodes have the same tag, both comments and none, define or undefine data, tag input must have the same type, and some other conditions that are not relevant to us will not be listed.

The entire VNode diff process

Before you understand the vnode diff, you must first understand what vnode is, how it is generated, and the relationship between Vnode and ELM

  1. If the two VNodes are SamevNodes, patch vNodes are performed
  2. Patch vnode process

(1) First, the ELM of VNode points to the ELM of oldVnode

(2) Update ELM attR, class, style, domProps, events, etc., using vNode data

(3) If vnode is a text node, then directly set the TEXT of ELM, the end

(4) If vNode is a non-text node &&oldVnode has no children, then ELM append directly

(5) If vNode is a non-text node &&oldVnode has children, then remove elm’s children node directly

(6) If non-text nodes && all have child nodes, then updateChildren enters the diff algorithm. The previous 5 steps exclude the case that diff cannot be performed

  1. Diff algorithm. Here, take the Web platform as an example

Again, the diff algorithm is passed to the children of two SamevNodes, so the easiest way to replace oldChildrenVnodes with newChildrenVnodes is to iterate over newChildrenVnodes, Just regenerate the HTML fragment, and everyone’s happy. But doing so would constantly create elements, which would have an impact on performance, so the old timers came up with the diff algorithm.

(1) Take the leftmost node of the two and determine whether it is sameVnode. If so, the second step of patch vnode above will be carried out. After the whole process is finished, the class, style, events, etc. of ELM have been updated. The children structure of ELM has also been updated through the whole process mentioned above. At this time, it is up to us to decide whether to move this ELM, because it is the leftmost node of children, so the position remains unchanged and the leftmost node moves forward one step

(2) If it is not the case described in (1), the rightmost node of the two is taken, which is the same as the judgment process of (1), but the position of the rightmost node is moved one step forward

(3) If not in the case described in (1) and (2), oldChildrenVnodes leftmost node and newChildrenVnodes rightmost node, same as in (1), but elm should be moved to the right of oldVnode leftmost elm, Since vNode takes the rightmost node, the position is unchanged if the rightmost node of oldVnode is sameVnode, so the rightmost node of newChildrenVnodes corresponds to the rightmost node of oldChildrenVnodes. However, the left-most node of oldChildrenVnodes is multiplexed, and the right-most node of oldChildrenVnodes has not been multiplexed, so it cannot be replaced, so it is moved to the right of elm, the right-most node of oldChildrenVnodes. The oldChildrenVnodes leftmost node position is then moved one step forward, and the newChildrenVnodes right-most node position is moved one step forward

(4) If it is not in the case described in (1), (2) and (3), oldChildrenVnodes and newChildrenVnodes are selected from the rightmost and leftmost nodes respectively. The position of elm needs to be moved to the left of oldChildrenVnodes elm, because vNode takes the leftmost node. If oldChildrenVnodes’ leftmost node is sameVnode, the position does not need to change. So the left-most node of newChildrenVnodes corresponds to the left-most node of oldChildrenVnodes, but since it is the right-most node of oldChildrenVnodes that is multiplexed, the left-most node of oldChildrenVnodes has not been multiplexed yet, It cannot be replaced, so move to the left of oldChildrenVnodes leftmost elm. The oldChildrenVnodes right-most node position is then moved one step forward, and the newChildrenVnodes left-most node position is moved one step forward

(5) If not in the case described in (1) (2) (3) (4), search oldChildrenVnodes for the oldVnode whose leftmost node is sameVnode with newChildrenVnodes, if not found, Create a new element with the new vNode, insert it in the same way as in (1), except to the left of the oldChildrenVnodes leftmost node. Because if newChildrenVnodes leftmost node and oldChildrenVnodes leftmost node are sameVnode, the location is unchanged and the elm of oldVNode found in oldChildrenVnodes is reused. Oldvnodes that have been reused will not be retrieved later. Then move the newChildrenVnodes leftmost node position forward one step

(6) after the above steps, the left-most node of oldChildrenVnodes or newChildrenVnodes overlapped with the right-most node and quit the cycle

(7) If the left-most node of oldChildrenVnodes overlapped with the right-most node first, newChildrenVNodes has nodes that have not been inserted, recursively create elements corresponding to these nodes, and then insert them to the right or right of oldChildrenVnodes. Because you’re moving from the start and end positions to the middle, think about it. If the first remaining node of newChildrenVNodes and the leftmost node of oldChildrenVnodes were sameVnode, the position would not change

(8) If the left-most and right-most nodes of newChildrenVnodes overlap first, it indicates that a structure in oldChildrenVnodes has not been reused, and the start and end positions are close to the middle. So the location that is not being reused is between the left-most and right-most nodes of oldChildrenVnodes. Delete elm for that node.

Here’s an example of a specific diff process (web platform) :

<div> ------ oldVnode1, newVnode1, element1 <span v-if="isShow1"></span> -------oldVnode2, newVnode2, element2 <div :key="key"></div> -------oldVnode3, newVnode3, element3 <p></p> -------oldVnode4, newVnode4, element4 <div V-if ="isShow2"></div> -------oldVnode5, newVnode5, element5 </div> // if isShow1=true, isShow2 =trueAnd the key ="aliarmo"The template will be portrayed as the following: < div > < span > < / span > -- -- -- -- -- -- -- -- -- -- -- -- -- -- element2 < div key ="aliarmo"> < / div > -- -- -- -- -- -- -- -- -- -- element3 < p > < / p > -- -- -- -- -- -- -- -- -- -- -- -- -- element4 < div > < / div > -- -- -- -- -- -- -- -- -- -- element5 < / div > / / change state, isShow1 =false, isShow2 =trueAnd the key ="wmy"<div> <div key="wmy"></div>------------element6
  <p></p>-------------------element4
  <div></div>---------element5
</div>
Copy the code

So how is the DOM structure generated after changing state?

IsShow1 =true, isShow2=true, key=”aliarmo” OldVnode1 oldVnodeChildren = [oldVnode2 oldVnode3, oldVnode4, oldVnode5]

The corresponding DOM structure is:

After changing state to isShow1=false, isShow2=true, key=”wmy”, a new vNode structure is generated

NewVnode1 newVnodeChildren = [newVnode3 newVnode4, newVnode5]

OldVnode2, newVnode3, not sameVnode

OldVnode5, newVnode5, sameVnode. The original Element5 location is not moved. The original DOM structure remains unchanged.

OldVnode2, newVnode3, not sameVnode

OldVnode4, newVnode4, sameVnode. The original dom structure remains unchanged.

OldVnode2, newVnode3, not sameVnode

OldVnode3, newVnode3, sameVnode, oldVnode3, newVnode3

OldVnode2, newVnode3, not sameVnode

OldVnode5, newVnode3, not sameVnode

If no oldVnodeChildren is found, create a new element Element6 with newVnode3 and insert it to the left of the element corresponding to the current oldVnode2. Dom structure changes newVnodeChildren ends overlap, exit the loop, delete the remaining unreused elements Element2, Element3

1.8 the core conclusion

Now we can finally figure out what’s going on in core, starting with new Vue()

  1. New Vue() or new custom component constructor (inherited from Vue)
  2. Initialize props, methods, computed, data, watch, and add Observe to state, call lifecycle created
  3. Start the mount component, making sure the Render function is generated before mounting
  4. Note that each Watcher corresponds to a component. Watcher responds to the change by re-executing render to generate vNodes for patch
  5. Render executes in the current component context (component instance) to generate the corresponding VNode structure
  6. If there is no oldVnode, patch is a deep traversal of vNode, generating specific platform elements, adding attributes and binding events to specific platform elements, calling the hook function provided by the custom instruction, and appending to the existing elements. In the traversal process, if a custom component is encountered, repeat from Step 1
  7. If there is an oldVnode, patch uses the VNode diff algorithm to tinker with the existing platform elements and does not create new platform elements until absolutely necessary
  8. When the state changes, notify watcher corresponding to the component where the state resides, and perform render again to generate vnode for patch, that is, go back to Step 4

2, the compiler

The Compiler part of the Vue is responsible for compiling the Template, generating the Render and staticRender functions, which are compiled once for permanent use, so we usually do this at build time to improve page performance. The render and staticRender functions generate vNodes to provide this layer of abstraction for core.

Template == AST == recursive ATS generates render and staticRender == vnodes

(1) The process of converting template into AST

Let’s get a feel for the AST, which describes the following template structure

// Vue template let template =<div class="Test" :class="classObj" v-show="isShow">

 {{username}}:{{password}}

 <div>

 <span>hahhahahha</span>

 </div>

 <div v-if="isVisiable" @click="onClick"></div>
 <div v-for="item in items">{{item}}</div>


 </div>

 `
Copy the code

The following is a brief description of the process for converting template to AST:

  1. If template is a string starting with <, determine whether it is a comment, a Doctype, an end tag, or a start tag.

(1) If it is an opening tag, it will handle strings similar to the following

<div class="Test" :class="classObj" v-show="isShow">
Copy the code

The tag and all attribute lists can be easily resolved through the re, and then the attribute lists can be classified to parse v-if, V-for and other instructions, events, special attributes, etc. Template removes the parsed part and returns to Step 1

(2) If it is a closing tag, it will process a string similar to the following. Again, template removes the parsed part and returns to Step 1

</div>
Copy the code
  1. If not, it is a string, which is treated like the interpolated string or plain text below. Again, template removes the parsed portion and returns to Step 1
{{username}}:{{password}} or username: passwordCopy the code
  1. If template is empty, parsing ends

(2) AST generates render and staticRender

It mainly traverses the AST (if you are interested, you can experience it yourself, for example: Traversing the AST to generate and restore the above template, I believe there will be a different experience), and splashes the string of rendering function according to the attributes of each node, for example: If there is V-if =”isVisiable” in the template, then the AST node has an IF attribute, which is available when the corresponding VNode is created

(isVisiable) ? _c('div') : _e()
Copy the code

Unavailable with with, unavailable value determines whether a VNode is created. Of course, some instructions cannot be processed at compile time. They will be mounted to the VNode when generating the VNode and further processed when parsing the VNode, such as V-show and V-ON.

Here are the render and staticRender generated by the above template:

/ / render function
(function anonymous() {
    with (this) {
        return _c('div', {
            directives: [{
                name: "show".rawName: "v-show".value: (isShow),
                expression: "isShow"}].staticClass: "Test".class: classObj
        }, [_v("\n " + _s(username) + ":" + _s(password) + "\n "), _m(0), _v(""), (isVisiable) ? _c('div', {
            on: {
                "click": onClick
            }
        }) : _e(), _v(""), _l((items), function(item) {
            return _c('div', [_v(_s(item))])
        })], 2)}})// staticRender
(function anonymous() {
 with (this) {
  return _c('div', [_c('span', [_v("hahhahahha")]])}})Copy the code

Where this is the component instance, _c and _v are used to create vnodes and strings respectively, and username and password are states passed in when defining the component and mounted on this.

3, platform

The platform module is platform-specific, here we can define platform-specific interfaces passing runtime and compile to implement platform-specific customization, thus bringing Vue capabilities to other platforms, most of the work is here.

What is passed in to the Runtime is how to create specific platform elements, the relationships between platform elements, how to append, insert, remove platform elements, attributes, event listening, etc. For the Web platform, we need to pass in document.createElement, document.createTextNode to generate HTML elements as we traverse the vNode. InsertBefore needed when mounting; The change of state causes the remove and Append of vNode diff. After generating HTML elements, use setAttribute and removeAttribute to manipulate attributes. AddEventListener and removeEventListener listen for events. Provide custom components and directives that are useful for web platforms

What we need to pass in compile is the processing of some special attributes or instructions at compile time. Such as the Web platform, need to class, style, model special processing, to distinguish from the general HTML attributes; Provide Web platform-specific directives, v-HTML (which is the innerHTML of the binding element when compiled), V-Text (which is the textContent of the binding element when compiled), and V-Model, which depend on the specific platform element.

Three, the application

Said so much, the ultimate purpose is to reuse Vue core and compile, in order to bring Vue or Vue class development experience on other platforms, the previous also said a lot of successful reuse examples, if you want to bring Vue development experience for a platform, you can refer to. Under the concept of big front-end, some day, car display screen, smart watch and other terminal interface can be developed with Vue, great. So, how do you reuse it? Of course I’m only saying mandatory, you can customize more and more complex features for specific platforms.

  1. Define the nodeOps required by vNodes to generate platform-specific elements, that is, to add, delete, alter, and review elements. For the Web, nodeOps is to create and move real DOM objects. For other platforms, you can define the operation method of these elements.

  2. Define the modules that vNode needs to generate platform-specific elements. For the Web, modules are methods for manipulating DOM properties.

  3. The platform needs to define its own $mount method for Vue to mount components to existing elements.

  4. Other methods, such as isReservedTag, are reserved tag names. Custom component names cannot be the same. MustUseProp, which determines that an element property must be bound to component state, not a constant, etc.

Four,

There is a saying in the software industry that there is no problem that can’t be solved by adding one layer of abstraction, if there is one, then there are two layers. This is exactly how Vue is designed (maybe Vue is also copied from others). Compile’s AST abstraction layer connects the template syntax to the render function, and through the VNode abstraction layer, core is separated from the concrete platform.

The ultimate purpose of this article is to let everyone understand the reuse of Vue source core and compile secondary development, can be done for the specific platform to bring Vue or Vue class development experience. Of course, this is just one line of thinking that can be applied to a new development style someday when the Vue style development experience falls out of favor.


Pay attention to [IVWEB community] public number to get the latest weekly articles, leading to the top of life!