Personal Blog

If there is a mistake, I hope you leave a message to give directions.

It’s a bit messy, with all kinds of methods interlaced, so it’s hard to sort out the order, please bear with me

The flow front

In the Vue source code, Utah uses Flow as a static type checking tool. Flow is a static type checking tool from Facebook.

Why Flow? JavaScript is known to be a weakly typed language.

So-called weak type refers to define variables, do not need to what type, in the process of the program is running automatically determine type, if a language can be implicitly converted all of its type, then its variables, expressions, such as when in operation, even if the type is not correct, also can get correctly by implicit conversion type, the user is concerned, As if all types can perform all operations, Javascript is called weakly typed.

This may be nice to use in the beginning, but when you’re working on a larger project, it’s not always clear what type of arguments you’re writing, and refactoring can be tricky.

Typescript and Flow were born out of this need. However, Typescript is expensive to learn and you don’t usually learn a language for convenience, so Facebook opened source Flow four years ago.

Why does Vue use Flow instead of Typescript? Yuxi zhihu’s answer is this.

Specific how to use, you can learn to view Chinese documents here. This article focuses on Vue source techniques, not too much explanation of Flow.

The project architecture

Vue. Js is a typical MVVM framework, the core idea is data-driven and componentization. DOM is a natural mapping of data, and in Vue you only need to modify the data to update the DOM. Componentization is to treat each independent function block or interactive module on the page as a component, and the page as a container, so as to achieve building block development. Let’s take a look at the directory structure

The directory structure

Vue source directory division of labor is clear. The whole catalogue is roughly divided into

  • benchmarks: Test the Demo when working with large amounts of data
  • dist: Version packages required by each environment
  • examples: Some practical Demo implemented with Vue
  • flow: Data type detection configuration
  • packages: Plug-ins that need to be installed separately for specific environments to run
  • src: the core of the entire source code.
  • script: NPM script configuration file
  • test: Test case
  • types: New typescript configuration

The core code is in the SRC directory, including instantiation, data responsiveness, template compilation, event center, global configuration, and so on.

Start at the entrance

From the compiler, go to the package.json file in the root directory, and you can see that there is a dev file in script that generates the rollup wrapper configuration,

rollup -w -c scripts/config.js --environment TARGET:web-full-dev
Copy the code

Rollup means it uses the rollup wrapper, -w means watch listens for file changes, c means config uses configuration files to package, Scripts /config.js rollup, –environment rollup, –environment rollup If we go to scripts/config.js, we can see that the environment variable argument has been brought in and the genConfig() function has been triggered

genConfig()

const opts = builds[name]
builds

web-full-dev
entry
format
dest
env
alias
banner
web/entry-runtime-with-compiler.js
web
resolve()

split
alias
src/platforms/web/entry-runtime-with-compiler.js
$mount
$mount
mount
runtime-only
$mount

It is not easy to modify the original logic, but you can store the original function and redeclare it.

The whole process

See probably whole flow first

  • First render, executecompileToFunctions()Parse the template to render fn (render function), skip this if renderFn already exists
  • RenderFn throughvm._render()Compile to Vnode, and while reading the variables,WatcherthroughObject.defindProperty()thegetMethod collection depends on the DEP and starts listening
  • performupdataComponent()First to vDOMpatch()Methods willvnodeRender into the real DOM
  • Mount the DOM to the node and wait for the data to change
  • dataProperty changes, first check whether there is a reference to the data value in the collected dependency, no matter if there is no reference, the existence of the triggerObject.defindProperty()thesetMethod modifies the value and executes_updataforpatch()updataComponent()Making component updates

Roughly divided into

Esm complete build: including template compiler, render process HTML string → render function → VNode → real DOM node

Runtime-only runtime build: no template compiler, render function → VNode → real DOM node

Explain the vocabulary

  1. templateTemplate: Vue template is based on pure HTML, based on the template syntax of Vue, can still be written as the previous HTML structure.
  2. AST Abstract syntax tree:Abstract Syntax TreeFor short, mainly do three steps
    1. Parse :Vue uses HTMLParserParse the HTML template into an AST
    2. Optimizer: Performs some optimization of the AST static node tag processing, extracts the largest static tree, when_updateWhen updating the interface, there will be a patch process, and the DIff algorithm will directly skip the static node, thus reducing the patch process and optimizing the patch performance
    3. GenerateCode: Generated based on the ASTrenderfunction
  3. RenderFn render function: Render function is used to generateVirtual DOM(vdom). Vue recommends using templates to build our application interfaces, which Vue compiles into the underlying implementationRenderFn functionOf course, we can also write rendering functions instead of templates for more control
  4. Virtual DOM (VDOM, also known as VNode) : Virtual DOM tree, VueVirtual DOM PatchingThe algorithm is based onSnabbdom libraryAnd on the basis of a lot of adjustments and improvements. Can only be executed with RenderFnvm._render()Generated,patchThe goals are allVnode, and everyVnodeIt’s globally unique
  5. Patch: VDOM has mentioned this above, but it is still necessary to say that patch is the most core method in the whole Virtaul-DOM, and its main function is toOld vnodeAnd the new VNodediffAt the end of the process, a new DOM node is generated throughupdataComponent()Method is re-rendered, which vue has done quite a bit of performance optimization for
  6. Watcher: There is one for each Vue component Watcher theWatcherWill be in the componentrenderCollect the data on which the component depends, and fire the component when the dependency is updatedvm._updatacallpatch()Diff and re-render the DOM.

Cut the crap and masturbate

mount

Vue.prototype.$mount = function( el? : string | Element, hydrating? : boolean ): Component { el = el && query(el) .....Copy the code

key? :value (key: value|void);

el? : string | Element is the flow of the grammar, said the incoming el string can be a string, Element and void type — undefined type, hydrating? Boolean Also, must be Boolean and undefined.

key:? value (key: value|void|null);

The key must be value or undefined and null.

Function ():value (:value) :Component indicates that the function return value must be of type Component.

Function (key: value1 | value2) (key: value1 | value2) said the key must be value1 or value2 type.

Compile RenderFn

El = el && query(el) validates the passed el element node, warns if the passed node container does not find one and returns a createElement(‘div’) new div.

// Check if the tag passed is body or the root of the page // warn against mounting on the root of the page because mounting replaces the node. Finally, the node is returnedif(el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
    
  const options = this.$options;    
  if(! Options. render) {// If the accepted value already has RenderFn written, do nothing. If render is not present, go to this logic to compile the template into RenderFnlet template = options.template
    if(template) { ... // We use idToTemplate() to parse the template, and return the innerHTML of the node}if (typeof template === 'string') {
        if (template.charAt(0) === The '#') {// If the first character fetched by the template is#
          template = idToTemplate(template)
          if(process.env.NODE_ENV ! = ='production'&&! Template) {// Warn (' template element not found or is empty:${options.template}`,
              this
            )
          }
        }
      }else if(template.nodetype) {// if there is a nodeType, it is a normal node, also return innerHTML template = template.innerhtml}else{// If there is no template, the template is invalidif(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if(el) {// If it's a node,get the HTML template fragment. GetOuterHTML () is compatible with the passed EL element. The final goal is to get the outerHTML of the node. HTML fragment Template = getOuterHTML(el)}if(template) {// Compile HTML to generate renderFn, assign to options, VM.$optionsThe render ofif(process.env.NODE_ENV ! = ='production'&& config.performance && mark) {// Start mark('compile'} /* compileToFunctions() will fetch RenderFn from the getOuterHTML template. Parsing HTML templates into abstract syntax trees (AST). * 2. Optimizer: Optimizes the AST. * 3. GenerateCode: Generate render function according to AST. */ const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render; // Finally assign the parsed renderFn to the current instance options.staticreNderfns = staticRenderFnsif(process.env.NODE_ENV ! = ='production'&& config.performance && mark) {// End mark('compile end') // Calculate the time difference according to mark() compilation process, which is used to check the stage rendering performance measure(' vue 'in console performance${this._name} compile`, 'compile'.'compile end'}}} // Return to the previously stored mount() method for mounting, if renderFn existed before this stepreturn mount.call(this, el, hydrating)
}
Copy the code

The most important thing here is that compileToFunctions() compiles template to RenderFn, which you can see via directory jump.

template
renderFn
return mount.call(...)

  import Vue from './runtime/index'
Copy the code

The compilation process is a little bit more complicated, but I’ll talk about that later. To the prototype of the found Vue method is not founded on this, we need to SRC/platforms/runtime/index at the next higher level. The js,

/ / configure the way some global Vue. Config. MustUseProp = mustUseProp Vue. Config. IsReservedTag = isReservedTag Vue. Config. IsReservedAttr = IsReservedAttr Vue. Config. GetTagNamespace = getTagNamespace Vue. Config. IsUnknownElement = isUnknownElement / / platform installation instructions and components Extend (Vue.options.components, platformComponents) extend(Vue.options.components, platformComponents) To prove that it is not a server render, add the __Patch__ method vue.prototype. __Patch__ =inBrowser ? Patch: noop // Mount$mountMethods. Vue.prototype.$mount = function( el? : string | Element, hydrating? : Boolean): Component {// This object must be returned in the browser environment. Runtime-only versions run directly to this objectinBrowser ? query(el) : undefined
  
  return mountComponent(this, el, hydrating)
}
Copy the code

The hydrating parameter can be understood globally as server rendering, which defaults to false. MountComponent (this, EL, hydrating) is a process of updating and watcher the component. Look at what the mountComponent does. Find the SRC/core/instance/lifecycle. Js, this file is responsible for the class function for instance life cycle.

export functionmountComponent ( vm: Component, el: ? Element, hydrating? : boolean ): Component { vm.$el= el // First put the vm.$elCache the incoming EL,$elNow for a real nodeif(! vm.$options.render) {// Because only renderFn is finally recognized, if not, create an empty node Vnode VM.$options.render = createEmptyVNode
    
    if(process.env.NODE_ENV ! = ='production') {// In the development environmentif ((vm.$options.template && vm.$options.template.charAt(0) ! = =The '#') ||
        vm.$optionsThe el | | el) {/ * (defines the template but the template first if notThe '#'If (element is not passed in), the runtime-only version is used, which does not compile by default. If compilation is required, the build will need to be replaced.else{warn(// Failed to mount component: template or renderFn is not defined'Failed to mount component: template or render function not defined.'}}} // Initialize beforMount lifecycle for the current instance before mounting.'beforeMount'); // Declares an updateComponent method, which is the method to update the component to be called by the Watcher instance. // Performance + Mark can be used to analyze the time spent by Vue components in different phases to know where to optimize.let updateComponent 
  if(process.env.NODE_ENV ! = ='production'Performance && mark) {updateComponent = () => {// Get component marker const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}` mark(startTag); Const vnode = vm._render(); // mark start node const vnode = vm._render(); // Generate a Vnode mark(endTag); // Mark the end node // do the performance name'vue ${name} render'This allows you to view application health, rendering performance, and finally remove tags and measures (' vue ') in Proformance${name} render`, startTag, endTag);
     
      mark(startTag);
      vm._update(vnode, hydrating);
     
      mark(endTag)
      measure(`vue ${name}patch`, startTag, endTag); }}else{updateComponent = () => {// define a render watcher function // vm._render() that calls the render function and returns a VNode. The // _update() method diff compares the new vNode with the old vNode. Vm._update (vm._render(), hydrating)}} /* Create a _watcher object and push the target into the DEP. The _watcher mounted on a VM instance calls the _watcher update method of the current VM primarily to update the DOM. Used to force updates. Why is it called forced update? * vm: current instance * updateComponent: used to update vNode to previous DOM * noop: used to update vNode to previous DOM An invalid function can be understood as an empty function * {before() {... }} : configure the beforeUpdate lifecycle hook function * if the instance is already mountedtrueWatcher's name is Watcher's name. Because computed attributes and if you want to configure watch in options also use new Watcher, add this to distinguish the three */ new Watcher(VM, updateComponent, noop, {before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')}}},true )
  hydrating = falseCreate() and beforeCreate()if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

This function is used to mount nodes and respond to data. As for why there is a judgment statement to declare the updateComponent method based on conditions, performance shows that one of the methods is used to test render and Update performance. Easy to view rendering performance in Chrome=> Performance

process.env.NODE_ENV ! = ='production' && config.performance && mark
Copy the code

Mark encapsulates a method. The specific API can refer to MDN Performance to mark the current element, and then return a specific time point. The main function is performance burying point

if(process.env.NODE_ENV ! = ='production') {// Check whether the browser runtime supports performace const perf =inBrowser && window.performance
  if( perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures ) { mark = tag => perf.mark(tag); Measure = (name, startTag, endTag) => {perf.measure(name, startTag, endTag) Perf. clearMarks(startTag) perf.clearMarks(endTag) perf.clearMeasures(name)}}}Copy the code

As for the vm._update() just now, lifecyle.js has been defined above

Vue.prototype._update = function(vnode: VNode, hydrating? Bool) {// first receive vnode const VM: Component = this const prevEl = vm.$el; Const prevVnode = vm._vnode; // old vnode const prevActiveInstance = activeInstance; // null activeInstance = vm; // Get the current instance vm._vnode = vnode; // The current vnodeif(! PrevVnode) {// If the old vnode that needs diff does not exist, __patch__ cannot be done // therefore a real DOM node VM needs to be created with the new vNode.$el = vm.__patch__(
                        vm.$el, // Real DOM node vnode, // passed vnode hydrating, // whether server renderfalse/* removeOnly is a special tag that is only used with <transition-group> to ensure that a correct relative position is maintained during the removal of elements. * /)}else{// If the prevVnode for diff exists, first diff the prevVnode and vNode and patch the dom operations to the prevVnode, and then complete the working VM for updating the real DOM.$el= vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance; // // if a real DOM node existsif(prevEl) {// Clear the previous __vue__ and mount the new prevEl.__vue__ = null} // Mount the updated VM to vm__vue__ cacheif (vm.$el) {
      vm.$el.__vue__ = vm} // If the current instance$vnodeAs with the parent component's _vNode, update it as well$el
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el}}Copy the code

How to patch?

__Patch__ is the most core method in the whole virtaul-DOM. The main function is to diff the prevVnode(old VNode) and the new vNode, compare the patch, and finally generate the new real DOM node to update the view of the changed part. In/Packages /factory.js, patch() is defined. There are too many codes, only important parts are extracted, and the process is clear. Vue2.0 + is the patch virtual DOM algorithm established by referring to SNabbDOM

return functionPatch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {// oldVnode: oldVnode, vnode: new vnode, hydrating: Server render, removeOnly: Avoid misoperation // If the new vnode does not exist and the old vnode exists, the old vnode is returned directly without patchif (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return} var insertedVnodeQueue = []; // If the old vNode does not existif(isUndef(oldVnode)) {// create a new node createElm(vnode, insertedVnodeQueue, parentElm, refElm); }elseVar isRealElement = isDef(oldvNode.nodeType); // If it is not a real DOM node and has the same attributesif(! IsRealElement && sameVnode(oldVnode, vNode)) {// Diff oldVnode and vNode, Patch patchVnode(oldVnode, VNode, insertedVnodeQueue, removeOnly) to oldVnode; }}} // Finally returns the contents of the new vNodereturn vnode.elm
  }
Copy the code

This is a basic patch, whose target is changed to patchVnode() of/SRC /core/vdom/patch.js, and the basic attributes of the old vnode and the new vnode can be pre-compared through sameVnode(). This method determines whether oldvNodes and vNodes need to be diff next

function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  )
}
Copy the code

Only when the basic attributes of the two Vnodes are the same, it is considered that the two vnodes are only partially updated, and then the two vnodes will be diff. If the basic attributes of the two Vnodes are inconsistent, the diff process will be directly skipped and a real DOM will be created based on the Vnode. Delete the old nodes as well. DomcreateElm (vNode, insertedVnodeQueue, parentElm, refElm); Create a new node. Conversely, there is an oldVnode. When both olDVNodes and vnodes exist and both sameVnode(oldVnode, vNode) nodes have the same basic properties, then the diff process of two nodes is entered.

Define patchVnode in/SRC /core/vdom/patch.js

functionPatchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { Delete) the whole process will in turn calls prepatch, update, postpatch hook function, such as in the compile phase generated some static subtree * in the process due to not change just skip the comparison, The core part of the dynamic subtree comparison process is when the old vNode and the new vnode exist children, update the child node with the updateChildren method. * @param oldVnode oldVnode * @param vnode new vnode * @param insertedVnodeQueue empty array, used for the lifecycle phase * @param removeOnly is a special tag that is used only with <transition-group> to ensure that a correct relative position is maintained during the removal of elements. * /if (oldVnode === vnode) {
      return} const elm = vnode.elm = oldvNode. elm // asynchronous placeholderif (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return} // If the new vnode and the old vnode are static nodes with the same key, or the new vnode is a one-time render or clone node, then the component instance is replaced and returnedif (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return} // You can scroll down to see the vNode example, where data is a node property, including class Style attr and instructionsletI const data = vnode. Data / / if the component instance attributes of existence and existence prepatch hook function is updated attrs/style/class/events/directives/refs propertiesif(isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, Vnode)} const oldCh = oldvNode. children const ch = vnode.children // If the new vNode has node attributes, isPatchable returns whether it contains the component instance tagif(isDef(data) &&isPatchable (vnode)) {// CBS saves the hooks function:'create'.'activate'.'update'.'remove'.'destroy'
      for(i = 0; i < cbs.update.length; + + I) CBS. Update [I] (oldVnode vnode) / / remove the CBS save update hook function, in turn, calls, attrs update/style/class/events/directives/refs propertiesif(isDef(I = data.hook) &&isdef (I = i.pdate)) I (oldVnode, vnode)} // If vnode has no text nodeif(isUndef(vnode.text)) {// If both the old vnode and the children of the new vnode existif(isDef(oldCh) &&isdef (ch)) {// If the children are different, updateChildren diff the childrenif(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)else if(isDef(ch)) {// Empty the text of the old nodeif (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' 'AddVnodes (elm, null, ch, 0, ch. Length-1, insertedVnodeQueue)else if(isDef(oldCh)) {oldchildren removeVnodes(elm, oldCh, 0, oldch.length-1);else if(isDef(oldvnode.text)) {nodeOps. SetTextContent (elm,' '} // If the text is different}else if(oldVnode.text ! == vnode.text) {// Update vnode's text content nodeops.settextContent (elm, vnode.text)} Indicates that patch is complete.if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }
Copy the code

  1. If two VNodes are equal, no patch is required.
  2. If it is asynchronous placeholder, executehydrateMethod or definitionisAsyncPlaceholderSet to true and exit.
  3. If both vNodes are static, there is no need to update them, so replace the previous vNodescomponentInstanceInstance is passed to the current VNode. Exit the patch
  4. performprepatchHook.
  5. Iterate through the call to the UPDATE callback and executeupdateHook. Attrs update/style/class/events/directives/refs properties.
  6. Execute if both vnodes have children, vnodes have no text content, and the two vnodes are not equalupdateChildrenMethods. This is the key to the virtual DOM.
  7. If the new vnode has children and the old one does not, empty the text and add the vNode node.
  8. If the old vnode has children and the new one does not, clear the text and remove the vNode.
  9. If neither vnode has children and the old vnode has text and the new vnode has no text, clear the DOM text content.
  10. Update the DOM element text content if the text of the old vNode is different from that of the new vNode.
  11. callpostpatchThe hook tells Patch that it is complete.

updateChildren

This is a little convoluted

functionupdateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, RemoveOnly) {/* * @parentelm parent element * @oldch old child * @newch new child * @insertedvNodeQueue Records all newly inserted nodes for call * @removeOnly Is a special flag used only by <transition-group> to ensure that deleted elements remain in correct relative positions */ while leaving the transitionletOldStartIdx = 0 //oldStartIdx => old header indexletNewStartIdx = 0 newStartIdx => new header indexletOldEndIdx = oldch.length-1 //oldEndIdx => old tail indexletOldStartVnode = oldCh[0] // oldStartVnode, firstletOldEndVnode = oldCh[oldEndIdx] // oldEndVnode, last oneletNewEndIdx = newch.length-1 //newEndIdx => new tail indexletNewStartVnode = newCh[0] // The first new index nodeletNewEndVnode = newCh[newEndIdx] // StartIndex, endIndex, startNode, endNode // 2. StartIndex, endIndex, startNode, endNode for the new child node arrayletOldKeyToIdx, idxInOld, vnodeToMove, refElm const canMove =! removeOnlyif(process.env.NODE_ENV ! = ='production') {// Check whether the new child is duplicateKeys (newCh)}while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if(isUndef(oldEndVnode)) {oldEndVnode = oldCh[--oldEndIdx] // If the first index is the same as the new one}else if(sameVnode(oldStartVnode, newStartVnode)) {// Diff updates the old and new header indexers, PatchVnode (oldStartVnode, newStartVnode, // oldStartVnode = oldCh[++oldStartIdx] // newStartVnode = newCh[++newStartIdx] // If the old tail index node is similar to the new tail index node, it can be reused}else if(sameVnode(oldEndVnode, newEndVnode)) {// patchVnode(oldEndVnode, newEndVnode, InsertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] / newEndVnode = newCh[--newEndIdx]}else if(sameVnode(oldStartVnode, newEndVnode)) {// Vnode moved right /* there is a case where * old [5,1,2,3,4] * new [1,2,3,4,5], 5->1,1->2... ? * even if has a key, also can appear,1,2,3,4 [5] = >,5,2,3,4 [1] = >,2,5,3,4 [1]... Patch patchVnode(oldStartVnode, newEndVnode, oldStartVnode, newEndVnode, oldStartVnode, newEndVnode, oldStartVnode, newEndVnode, oldStartVnode, newEndVnode, oldStartVnode, newEndVnode, oldStartVnode, newEndVnode) InsertedVnodeQueue) // The old VNode starts to insert into the real DOM, the old head moves to the right, InsertBefore (parentElm, oldStartvNode. elm, nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if(sameVnode(oldEndVnode, newStartVnode)) { Patch patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) // The old vNode starts to insert into the real DOM, The new head moves to the left, CanMove && nodeops. insertBefore(parentElm, oldEndvNode. elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else{// If none of the above criteria is correct, we need a key-index table to reuse the maximum number of nodesif(isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, OldEndIdx) // Find the location of the new node in the old node group idxInOld = isDef(newStartvNode.key)? oldKeyToIdx[newStartVnode.key] : FindIdxInOld (newStartVnode, oldCh, oldStartIdx, oldEndIdx) // If the new node does not exist in the old node, we create a new element, we insert it before the old first index node (createElm 4)if (isUndef(idxInOld)) { // New element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else{// If the old node has this new node vnodeToMove = oldCh[idxInOld] // Compare the new node with the new first index, if the same type of patchif(sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, InsertedVnodeQueue) insertedVnodeQueue = insertedVnodeQueue OldCh [idxInOld] = undefined // If there is no group offset, CanMove && nodeOps. InsertBefore (parentElm, vnodetomove.elm, oldStartvnode.elm)}else{// Create nodes with different types. // Same key but different element. treat as new Element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm,falseNewStartVnode = newCh[++newStartIdx]} // If the old index is larger than the old index, the old node group has been traversed. Add the remaining new VNodes to the location of the last new nodeif(oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : NewCh [newEndIdx + 1]. Elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)} Then, the remaining nodes in the old node group are not needed, so they are directly deletedelse if (newStartIdx > newEndIdx) {
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
Copy the code

Vnode

The vnode attributes are defined in/SRC /core/vdom/vnode.js

export default class VNode {
  constructor( tag? : string, data? : VNodeData, children? :? Array<VNode>, text? : string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? : Function ) {this.tag = tag // Tag attributes
    this.data = data  // Render to real DOM, node on class attr style events etc...
    this.children = children // Child node, also on vnode
    this.text = text  / / text
    this.elm = elm  // corresponds to the actual DOM node
    this.ns = undefined // Namespace of the current node
    this.context = context // Compile scope
    this.fnContext = undefined // Functional component context
    this.fnOptions = undefined // Functional component configuration items
    this.fnScopeId = undefined // The functional component ScopeId
    this.key = data && data.key  // It exists only under bound data, which can improve performance during diff
    this.componentOptions = componentOptions // The vnode object generated by the VUE component. If the vNode is generated by the normal DOM, this value is null
    this.componentInstance = undefined  // The current component instance
    this.parent = undefined // VNodes, placeholder nodes for components
    this.raw = false    // Whether it is native HTML or plain text
    this.isStatic = false  / / static node id | | keep alive
    this.isRootInsert = true    // Whether to insert it as the root node
    this.isComment = false  // Whether it is a comment node
    this.isCloned = false  // Whether it is a clone node
    this.isOnce = false    // Whether it is a V-once node
    this.asyncFactory = asyncFactory // Asynchronous factory method
    this.asyncMeta = undefined Asynchronous Meta / /
    this.isAsyncPlaceholder = false // Whether it is an asynchronous placeholder

  }

  // Backwards-compatible alias for the container instance
  get child (): Component | void {
    return this.componentInstance
  }
}
Copy the code

Tag, data, children, key and text are the most important attributes. Vnodes can be specified in the following categories

  • TextVNode Text node.
  • ElementVNode Common element node.
  • ComponentVNode Component node.
  • EmptyVNode has no comment node for content.
  • CloneVNode can be a clone node of any of the above types. The only difference is that the empirical property of isverification is true. We first define a VNode
 {
    tag: 'div'
    data: {
        id: 'app',
        class: 'test'
    },
    children: [
        {
            tag: 'span',
            data:{
                
            },
            text: 'this is test'}}]Copy the code

Each layer object is a node. vnode

    {
        tag:'label 1'Attrs :{attribute key1: attribute value1, attribute key2: attribute value2,... }, children:[ { tag:'Child tag 1'Attrs :{subattribute key1: subattribute value1, subattribute key2: subattribute value2,... }, children:[ { .... } ] }, { tag:'Subtag 2'Attrs :{subattribute key1: subattribute value1, subattribute key2: subattribute value2,... }, children:[ { .... } ] } ] }Copy the code

Produces the final render in a nested recursive fashion

<div id="app" class="test">
    <span>this is test</span>
</div>
Copy the code

The entire VNode tree built up by the Vue component tree is unique. This means that the handwritten render function cannot be componentized

render: function (createElement) {
  var myVnode = createElement('p'.'hi')
  return createElement('div', [
    myVnode, myVnode
  ])
}
Copy the code

The official way to do it is to use factory functions

render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p'.'hi')}}))Copy the code

Why would you do that? CreateElement creates a child Vnode object that is already unique. If you use it again in a component, it will not be unique.

Took a look. Got a way in. By the way, if you think this is too much trouble to write, how easy it is to just say Array(20).map(). but

new Array(20).map(function(v,i){ console.log(v,i); // output nothing,})Copy the code

Map traverses only subscripts with values (including undefined), and finally forms an Array in sequence. If new Array(20) is used, the values in the Array are not initialized, and the output is [empty * 20]. So the map doesn’t print anything, because it’s not initialized, it’s null, it’s skipped.

But, the second option, Array. Apply (null,} {length: 20), the output for [20], undefined undefined undefined… *, a value has been initialized, contains 20 undefined Array. Plus the map (), that is to say, every time is Array. Apply (null, [] undefined undefined,… Array(undefined,undefined… *20), create 20 vNodes by looping through createElement via return

Why is it so complicated? ES6 array. from can do this, but the author should consider compatibility or something that can be done with ES5. Marvel at the basic skills of Utah…

CompileToFunctions (template compiles to render)

First in/SRC/platforms/web/compiler/index, js is defined compileToFunctions () method,

Import {baseOptions} from; import {baseOptions} from; import {baseOptions} from'./options'
import { createCompiler } from 'compiler/index'// Generate AST and Render const {compile, compileToFunctions} = createCompiler(baseOptions)export { compile, compileToFunctions }
Copy the code

Look at the imported configuration first

export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules,
  directives,
  isPreTag,
  isUnaryTag,
  mustUseProp,
  canBeLeftOpenTag,
  isReservedTag,
  getTagNamespace,
  staticKeys: genStaticKeys(modules)
}
Copy the code

You can see that compile and compileToFunctions are defined, the former being the AST syntax tree and the latter being the compiled renderFn

import { parse } from './parser/index'// parse HTML template as AST import {optimize} from'./optimizer'Import {generate} from; import {generate} from'./codegen/index'Import {createCompilerCreator} from render function import {createCompilerCreator} from'./create-compiler'// Allows the creation of an alternative compiler, where the default compiler is exported using only the default widgetexport const createCompiler = createCompilerCreator(functionbaseCompile ( template: string, options: CompilerOptions ): Procedure for CompiledResult {// parseHTML, import configuration, remove whitespace from template, parse to AST, Const AST = parse(template.trim(), options) console.log(AST) // By default, markup processing is optimized, otherwise it is notif(options.optimize ! = =false) {optimize(ast, options)} RenderFn const code = generate(ast, options) console.log(code.render) // throwreturn { 
    ast, 
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Copy the code

CreateCompilerCreator () takes a function argument createCompiler is used to create a compiler and returns compile and compileToFunctions.

export function createCompilerCreator (baseCompile: Function): Function {
  return function createCompiler (baseOptions: CompilerOptions) {
    functionCompile (template: string,// template options? : CompilerOptions // compiler configuration): CompiledResult {// Call finalOptions' implicit __proto__ to baseOptions const finalOptions = object.create (baseOptions) const errors = [] const tips = [] finalOptions.warn = (msg, tip) => { (tip ? Tips: errors).push(MSG)} // Merge the configuration if it is importedif// Merge branch modulesif(options. Modules) {finalOptions. Modules = (baseOptions. Modules | | []). The concat (options. Modules)} / / merge custom commandsif(options.directives) { finalOptions.directives = extend( Object.create(baseOptions.directives || null), Caching)} // Merge other configurationsfor (const key in options) {
          if(key ! = ='modules'&& key ! = ='directives') {finalOptions[key] = options[key]}}} Const compiled = baseCompile(template, finalOptions)if(process.env.NODE_ENV ! = ='production') {
        errors.push.apply(errors, detectErrors(compiled.ast))
      }
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }
    
    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
Copy the code

Finally, after execution at the compile() level, the compile function is thrown

Compile is a compiler that converts the passed template into the corresponding AST tree, renderFn, and staticRenderFns functions

CompileToFunctions, by performing createCompileToFunctionFn (compile), createCompileToFunctionFn () is a cache to the compiler. StaticRenderFns and renderFn are also converted to Funtion objects. It will compile

Different platforms have different options, so createCompiler passes ina baseOptions based on the platform. It merges the options passed in by compile to get the finalOptions.

export functionCreateCompileToFunctionFn (compile: Function) : the Function {/ / statement cache const cache = Object. The create (null)return functioncompileToFunctions ( template: string, options? : CompilerOptions, vm? : Component ): CompiledFunctionResult {// merge config options = extend({}, Options) const warn = options. Warn | | baseWarn delete options. Warn / / try to detect CSP development environment, similar to the user's browser Settings, need relaxing restrictions or cannot be compiled, under normal circumstances can be ignoredif(process.env.NODE_ENV ! = ='production') {
      // detect possible CSP restriction
      try {
        new Function('return 1')
      } catch (e) {
        if (e.toString().match(/unsafe-eval|CSP/)) {
          warn(
            'It seems you are using the standalone build of Vue.js in an ' +
            'environment with Content Security Policy that prohibits unsafe-eval. ' +
            'The template compiler cannot work in this environment. Consider ' +
            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
            'templates into render functions.'Delimiters? String(options.delimiters) + template: template const key = options.delimiters? String(options.delimiters) + template: templateif (cache[key]) {
      return} // Const compiled = compile(template, options)if(process.env.NODE_ENV ! = ='production') {
      if (compiled.errors && compiled.errors.length) {
        warn(
          `Error compiling template:\n\n${template}\n\n` +
          compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
          vm
        )
      }
      if(compiled.tips && compiled.tips.length) { compiled.tips.forEach(msg => tip(msg, Const fnGenErrors = [] const fnGenErrors = [] // Render to Funtion res.render = createFunction(compiled.render, FnGenErrors) / / all staticRenderFns into object Funtion res. StaticRenderFns = compiled. StaticRenderFns. The map (code = > {returnCreateFunction (code, fnGenErrors)}) // Check for function generation errors. This occurs only if the compiler itself has an error. The author is mainly used for CodeGen developmentif(process.env.NODE_ENV ! = ='production') {
      if((! compiled.errors || ! compiled.errors.length) && fnGenErrors.length) { warn( `Failed to generate renderfunction:\n\n` +
          fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), vm)}} // Finally stored in the cache, the next use can be readreturn (cache[key] = res)
  }
}
Copy the code

const cache = Object.create(null)

Why not just const cache = {}? Let’s feel it

const cache = {}
Object.prototype
Object.create(null)
for.. in
Object.create(null)
hasOwnProperty

HTML turn RenderFn

So let’s do some code

<div id="app"></div>
  <script>
      var vm = new Vue({
        el:'#app',
        template:`
          <div @click="changeName()">
            <span>{{name}}</span>
            <ul>
              <li v-for="(item,index) in like" :key="index">{{item}}</li>
            </ul>
          </div>`,
        data:{
          name:'Seven',
          like:['travel'.'movie'.'skiing']
        },methods:{
          changeName(){
            this.name = 'Floyd'
          }
        }
      })
    </script>
Copy the code

Let’s take a look at his AST syntax tree,

Maybe you see a little dizzy, ok, we don’t need to care about this, abstract, can let you understand also called abstract? Let’s take a look at the render function

with(this){return _c('div',{on:{"click":function($event){changeName()}}},[_c('span',[_v(_s(name))]),_v(""),_c('ul',_l((like),function(item,index){return _c('li',{key:index},[_v(_s(item))])}))])}
Copy the code

In order to make it easy for you to see the structure clearly, take pains to manually format the following

with(this) {
      return _c('div', 
                {
                  on: {
                    "click": function ($event) {
                      changeName()
                    }
                  }
                }, 
                [
                  _c('span', [ _v(_s(name)) ]), 
                  _v(""), 
                  _c('ul', 
                      _l( (like), function (item, index) {
                      return _c('li', 
                                  {
                                    key: index
                                  }, 
                                  [
                                    _v( _s(item) )
                                  ]
                                )
                    })
                  )
                ]
              )
    }
Copy the code

Maybe some people think it’s harder to understand, it’s ok, this logic can be understood.

_c(
    'Tag name'// Bind attribute 1: value, attribute 2: value,... }}, [// child _c('Tag name'// Bind subattribute 1: value, subattribute 2: value,... }}, [// subtag...] }])Copy the code

Compile renderFn into a Vnode

_c = this._c = vm._c; we print vm._c

Avoid using the with() syntax in your functions, as it can make your application undebuggable. But this is what Rain Creek does, using closures to wrap it inside functions without worrying about leakage.

ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); } / SRC/core/instance/render. Js definition / / this method will be bound to the createElement method function instance in order to obtain renderFn context in it. vm._c = (a, b, c, d) => createElement(vm, a, b, c, d,false)
Copy the code

Points to the createElement() function, which in turn points to _createElement(), defined at/SRC /core/vdom/create-element.js. The result is a Vnode. This function definition can be jumped to see other functions in this directory. We can find the definition in /rc/core/instance/render-helper/index.js

export functioninstallRenderHelpers (target: Any) {target._o = markOnce // v-once static component target._n = toNumber // Parse isNAN target._s = toString Target. _t = renderSlot // Slot target._q = looseEqual // Target. _m = renderStatic // renderStatic content target._f = resolveFilter _k = checkKeyCodes // Checks eventKeyCode for the presence of target._b = from the config configurationbindObjectProps // Merge the V-bind instruction to VNode target._v = createTextVNode ResolveScopedSlots // handle ScopedSlots target._g =bind// Process event bindings}Copy the code

createElement

var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2;

functionCreateElement (context, tag, data, children, normalizationType, alwaysNormalize)if(Array.isArray(data) || isPrimitive(data)) { normalizationType = children; children = data; data = undefined; } // If alwaysNormalize istrue// normalizationType should be set to the constant value of ALWAYS_NORMALIZEif(isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; } // Call _createElement to create a virtual nodereturn _createElement(context, tag, data, children, normalizationType)
}

function_createElement (context, tag, data, children, normalizationType) {/* * If data.__ob__ exists, * Data cannot be used as a virtual node * a warning is thrown and an empty node is returned * * Monitored data cannot be used as vNode rendered data for the following reasons: Data may be changed during vNode rendering, which triggers monitoring and results in undesired operations * */if (isDef(data) && isDef((data).__ob__)) {
    "development"! = ='production' && warn(
      "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" +
      'Always create fresh vnode data objects in each render! ',
      context
    );
    returnCreateEmptyVNode ()} // When the component's IS property is set to onefalseThe value of theif(isDef(data) && isDef(data.is)) { tag = data.is; } // Vue will not know what to render this component into, so render an empty nodeif(! tag) {returnCreateEmptyVNode ()} // If the key is the original value, warning that the key cannot be the original value, must be string or numberif ("development"! = ='production'&& isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) { { warn('Avoid using non-primitive value as key, ' +
        'use string/number value instead.', context ); }} // Scope slots // If the child element is an array and the first is renderFn, move it to scopedSlotsif (Array.isArray(children) &&
    typeof children[0] === 'function') { data = data || {}; data.scopedSlots = { default: children[0] }; children.length = 0; } // Depending on the value of normalizationType, select different processing methodsif (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children);
  } else if(normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children); } var vnode, ns; // If the label name is stringif (typeof tag === 'string') { var Ctor; // Get the namespace ns = (context) if it currently has its own vNode and namespace or gets the tag name.$vnode && context.$vnode.ns) || config.getTagNamespace(tag); // Check whether the label is reservedif(config. IsReservedTag (tag)) {/ / if they are retained label, creates such a vnode vnode = new vnode (config. ParsePlatformTagName (tag), data, children, undefined, undefined, context ); // If not, then we will try to find the definition of this tag from the COMPONENTS of the VM instance, custom component}else if (isDef(Ctor = resolveAsset(context.$options.'components'Vnode = createComponent(Ctor, data, Context, children, tag); vNode = createComponent(Ctor, data, Context, children, tag); }else{// Guarantee scheme, // Unknown or unlisted namespaced elements // Check at Runtime because it may get a namespace when its // parent normalizes children vnode = new VNode( tag, data, children, undefined, undefined, context ); }}else{// direct component options/constructor // When tag is not a string, it is the component's constructor class. Vnode = createComponent(Tag, data, context, children); } // If vNode is an array, return it.if (Array.isArray(vnode)) {
    returnVnode}else if(isDef(vnode)) {// If there is a namespace, apply the namespace and return the vnodeif(isDef(ns)) { applyNS(vnode, ns); } // If the data is defined, it is traversed in depth, for either class or styleif (isDef(data)) { registerDeepBindings(data); }
    return vnode
  } else{// Create an empty VNodereturn createEmptyVNode()
  }
}
Copy the code

Look at the flow chart

new Vue

Find the SRC/core/instance/index. Js

this._init(options)

Calling the constructor from new goes through four steps:

  • Create a new object;
  • Assign the constructor’s scope to the new object (so this refers to the new object);
  • Execute the code in the constructor (add attributes to the new object);
  • Returns a new object. whileinstanceofUsed to detect the Vue constructorprototypeDoes it existthisThe prototype chain, in other words, if usednewWhen I instantiate,thisI’m pointing to this newly created objectthis instanceof VueDetermine whether the newly created object is of type Vue, which is equivalent to determining whether the newly created object is of type VueconstructorIs a Vue constructor.

To be continued… Continuously updated