This article focuses on the overall process of Vue2 rendering, including the realization of data response (bidirectional binding), template compilation, virtual DOM principle, etc., hoping that readers can gain something after reading it.

Blog sync: imhjm.com/article/59b…

preface

This part introduces some features of the front-end mainstream framework to improve our understanding of the framework, and finally leads to the overall introduction of the principle of VUE2. Refer to Yuxi live not blowing not black talk about the front-end framework interested students can listen

Modern mainstream frameworks all use a data => view approach to hide cumbersome DOM operations, and adopt Declarative Programming instead of jquery-like Imperative Programming.

$("#xxx").text("xxx");
// Becomes lower
view = render(state);Copy the code

In the former, we write in detail how to manipulate the DOM node. It does whatever we command it. In the latter, we input the data state and output the view (we don’t care about the intermediate process, it’s all done by the framework). The former is straightforward, but when the application becomes complex the code becomes difficult to maintain, while the latter framework enables us to implement a series of operations without managing the process, which has obvious advantages.

In order to achieve this, which is how to input data, output view, we will notice the above render function, the implementation of the render function, mainly on the DOM performance optimization, of course, there are a variety of implementation methods, The performance of the innerHTML directly, using The documentFragment, and the Virtual DOM varies from scenario to scenario, but the framework’s goal is that in most scenarios the framework already meets your optimization needs, which we won’t go into here, but we’ll cover later.

Of course, there is also the re-render view of data change detection. In data change detection, it is worth mentioning the connection between the Producer and the Consumer. Here, we can take the system (view) as a data Consumer for the moment, and our code sets the change of data. As data producers, we can be divided into system imperceptive data changes and system perceptive data changes

In rx.js, the communication between the two is divided into Pull and Push, which is not easy to understand, so I have a class here

  • The system cannot detect data changes

Frameworks like React/Angular don’t know when data has changed, but their views are updated. React signals the system with setState and renders the view using the virtual DOM diff. Angular has a dirty check process, traversing and comparing

  • The system is aware of data changes

Rx.js/vue is responsive, subscribing to observables, observers (or watcher) via Observer mode (for example, view renderers can subscribe to data as an Observer, as described below). The system can know exactly where the data has changed, so that the view can be updated.

The upper system is not able to perceive data changes, coarse granularity, sometimes have to manually optimize (such as pureComponet and shouldComponentUpdate) to skip some view data will not update to improve performance, the lower system can perceive data changes, fine granularity, but bind a large number of observers, There is a significant memory overhead that depends on tracing

so

Vue2, the protagonist of this paper, adopts a middle granularity approach, granularity to component level. Watcher subscribing data. When data changes, we can know which component data has changed, and then use virtual DOM diff to update corresponding components.

We’ll also expand on how it does this later, but we can start with a simple application.

Let’s start with a simple application

<div id="app"> {{ message }} </div> var app = new Vue({ el: '#app', data: { message: 'Hello Vue! ' } }) app.message = `xxx`; // The view was found to have changedCopy the code

From here we can also raise a few questions, to make the analysis of the latter principle more targeted.

  • Data response? How do I know about data changes?

    One more small detail: how does app.message get messages from Vue Data?

  • How do data changes relate to views?
  • What is the Virtual DOM? What is virtual Dom Diff?

Of course, we will also introduce some concepts related to collecting dependencies.

Principle of data response

Object.defineProperty

The core of Vue data response is to define or modify properties in an Object using the Object.defineProperty method (IE9+). The key to accessing descriptors are get and set, which are provided to property getters and setters

As you can see in the following example, we intercepted the data fetching and setting

var obj = {};
Object.defineProperty(obj, 'msg', {
  get () {
    console.log('get')
  },
  set (newValue) {
    console.log('set', newValue)
  }
});
obj.msg // get
obj.msg = 'hello world' // set hello worldCopy the code

Mention that little detail in passing

How do APP. message get messages from Vue Data?

It is also related to Object.defineProperty that Vue iterates through the data proxy when initializing the data

function initData (vm) {
    let data = vm.$options.data
    vm._data = data
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
        const key = keys[i]
        proxy(vm, `_data`, key)
    }
    observe(data)
}Copy the code

What does the proxy do?

function proxy (target, sourceKey, key) {
    Object.defineProperty(target, key, {
      enumerable: true.configurable: true,
      get () {
        return this[sourceKey][key]
      }
      set () {
        this[sourceKey][key] = val
      }
    })
}Copy the code

DefineProperty adds another layer of access so we can use app.message to access app.data.Message as an Object.defineProperty applet

Now that we’ve gotten to the core of this syntax, how do we know that the data has changed, but the response, there is a response, how does Vue implement the data response? How do you implement $watch?

const vm = new Vue({
  data: {msg: 1,
  }
})
vm.$watch("msg", () = >console.log("The MSG has changed"));
vm.msg = 2; // Print "MSG changed"Copy the code

Observer Mode (Observer, Watcher, Dep)

Vue implementation of responsive classes there are three very important classes, Observer class, Watcher class, Dep class I here first general introduction (detailed visible source English annotations)

  • The Observer class is primarily used for data to the VuedefinePropertyAdd getter/setter methods and collect dependencies or notify updates in getter/setter
  • The Watcher class is used to observe changes in data (or expressions) and then execute callbacks (which also collect dependencies), mainly on the $Watch API and instructions
  • The Dep class is an observable that can be subscribed to with different instructions (it is multicast)

The observer pattern, with the publish/subscribe pattern is a bit like But actually slightly different, publish/subscribe model is a unified event dispatch center, on add events to the center of the array (subscription), emit the array from the heart out events (released), publish and subscribe and after the release of scheduling the subscriber’s operations are performed by center of unity

However, there is no such center in the observer mode. The observer subscribes to the observable, and when the observable publishes events, it directly schedules the behavior of the observer. Therefore, the observer and the observable actually have a dependency relationship, which is not reflected in the publish/subscribe mode.

Dep actually stands for dependence

How do you implement the observer pattern?

The Dep can be subscribed by more than one Watcher, so it has an array of subscribers. If you subscribed to it, you put Watcher in the array.

class Dep {
  constructor () {
    this.subs = []
  }
  notify () {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
        subs[i].update()
    }
  }
  addSub (sub) {
    this.subs.push(sub)
  }
}

class Watcher {
  constructor () {
  }
  update () {
  }
}

let dep = new Dep()
dep.addSub(new Watcher()) // Watcher subscribes to dependenciesCopy the code

Now that we’ve implemented subscription, what about notification publishing, which is notify?

We can contact the data response from here, what we need is the data change to notify the update, which is obviously implemented in the setter in defineProperty, you should be smart enough to think that we can treat each data as a Dep instance and notify it when we setter. So we can defineProperty new Dep() and use the closure setter to get the Dep instance

It looks like this

function defineReactive (obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true.configurable: true.get: function reactiveGetter () {
            / /...
        },
        set: function reactiveSetter (newVal) {
            / /...
            dep.notify()
        }
    })
}Copy the code

And then there’s the problem of how do I get my Watcher instance to subscribe to this Dep instance when you put the Dep instance in there? Vue has done a nice trick here by getting the Dep instance out of get, so when you do watch, Perform the fetch value, triggering the getter to collect the dependency

function defineReactive (obj, key, val) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)

    const getter = property && property.get
    const setter = property && property.set

    let childOb = observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true.configurable: true.get: function reactiveGetter () {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend() // Equivalent dep.addSub(dep.target), collected here
            }
            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            if (newVal === value) {
                return
            }
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            dep.notify()
        }
    })Copy the code

We’re also going to look at this in conjunction with Watcher’s implementation

class Watcher(a){
  constructor (vm, expOrFn, cb, options) {
    this.cb = cb
    this.value = this.get()
  }
  get () {
    pushTarget(this) // Mark the global variable dep.target
    let value = this.getter.call(vm, vm) / / triggers the getter
    if (this.deep) {
      traverse(value)
    }
    popTarget() // Mark the global variable dep.target
    return value
  }
  update () {
    this.run()
  }
  run () {
      const value = this.get() // new Value
      // re-collect dep
      if(value ! = =this.value ||
          isObject(value)) {
          const oldValue = this.value
          this.value = value
          this.cb.call(this.vm, value, oldValue)
      }
  }
}Copy the code

So we’re going to do an evaluation when we do a new Watcher, and then because we’re marking the Watcher trigger, we’re collecting the dependency, so the observer is subscribing to the dependency. (This evaluation might fire more than one getter, it might fire multiple getters, so we’re collecting multiple dependencies.) Notice again that the run operation is what Watcher does after dep.notify(), and that there is a get operation. Notice that there is a new wave of dependencies collected! (Of course, there are related de-duplication operations)

Let’s go back to the little example we were trying to solve

const vm = new Vue({
  data: {
    msg: 1,
  }
})
vm.$watch("msg", () = >console.log("The MSG has changed"));
vm.msg = 2; // Output "changed"Copy the code

New Watcher(vm, ‘MSG ‘, () => console.log(” MSG changed “))

  • First, new Vue iterates through the data, adding getter/setter methods to the data defineProperty
  • weNew Watcher (vm, 'MSG' () = > console. The log (" MSG changed "))Target = null; Dep = null; Dep = null; Dep = null
  • Finally, we set vm. MSG = 2, which triggers the setter, dep.notify in the closure, to iterate through the subscriber array and perform the corresponding callback.

In fact, at this point, the core of the reactive principle is pretty much covered.

But object.defineProperty isn’t everything,

  • Array push/pop, etc
  • Cannot monitor array length changes
  • Array arr[XXX] = yyy not sensed
  • Similarly, the addition and deletion of object attributes are not perceived

To solve these problems of their own JS limitations

  • Vue first is the array method for variation, with__proto__Inherit those methods (if not, add one defineProperty directly to the array) and mutate by adding a dep.notify operation to the end
  • As for adding and deleting attributes, we can imagine that if we add attributes, we do not have defineProperty at all. If we delete attributes, we will delete our previous defineProperty, so here we add a $set/$delete API to implement these operations. Dep. Notify is also added at the end
  • Of course, this is not done by simply using the DEP for each data in defineProperty. There is also an instance of DEP in the Observer class and one is mounted to the data__ob__Property to obtain its Observer instance, such as arrays and objects above special operations, watch will collect this dependency when collecting, and then use this deP to notify update

    This section is not detailed, interested readers can read the source code

Here we can mention a new ES6 feature Proxy, which is likely to be the next generation of response mechanism, because it can solve the above defects, but due to compatibility issues can not be used well, let us look forward to ~

Now let’s take a look at this image from the Vue website


How does data relate to views

I’ve picked out a key piece of Vue code

class Watcher(a){
  constructor (vm, expOrFn, cb, options) {
  }
}
updateComponent = (a)= > {
   // Hydrating SSR is not covered in this paper
    vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
// noop is the callback function, which is nullCopy the code

This is the core relationship between Watcher and Render

Remember, when you do new Watcher, there’s an evaluation, which is a function expression, which is updateComponent, and when you do updateComponent, _update(vm._render(), hydrating), and collect dependencies. There are two key points here. What is vm._update doing?

vm._render

Let’s see what vue.prototype. _render is

  Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const {
      render,
      staticRenderFns,
      _parentVnode
    } = vm.$options
    // ...
    let vnode
    try {
      // vm._renderProxy is used as a VM for warning
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {

    }

    // set parent
    vnode.parent = _parentVnode
    return vnode
  }Copy the code

So here we can see that the render function is executed, which comes from options, and then returns vNode

So at this point we can turn our attention to where does the render function come from

If you are familiar with Vue2, you may know that Vue provides an option called render as this function. If it does not provide this option, let’s look at the lifecycle



Compile template into render function

Template compilation

One more piece of core code

const ast = parse(template.trim(), options) // Build an abstract syntax tree
optimize(ast, options) / / optimization
const code = generate(ast, options) // Generate code
return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
}Copy the code

We can see that it’s divided into three parts

  • Convert templates to abstract syntax trees
  • Optimize the abstract syntax tree
  • Generate code from an abstract syntax tree

So what exactly does it do? I’m going to go through it briefly

  • The first part is actually a variety of regees, including the matching of left and right open and closed labels and the collection of attributes. In the form of stack, the matching and the replacement of parent nodes are continuously pushed out of the stack. Finally, an object containing children is generated, and the object containing children also includes children
  • The second part is based on the first part, according to the node type to find some static nodes and mark
  • The third part is to generate the render function code

So you end up with something like this

The template

<div id="container">
  <p>Message is: {{ message }}</p>
</div>Copy the code

Generating the render function

(function() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "container"
            }
        }, [_c('p', [_v("Message is: " + _s(message))])])
    }
}
)Copy the code

Here we can combine the above code again

 vnode = render.call(vm._renderProxy, vm.$createElement)Copy the code

Where _c is vm.$createElement

Let’s move the virtual DOM implementation to the next section to avoid interfering with our Vue2 mainline

Vm.$createElement is an API for creating vNodes

Now that vm._render() has created vNode returns, vm._update is the next step

vm._update

The vm._update function is also related to the Virtual DOM. In the next section, we can first reveal the function, as the name indicates, is to update the view, according to the incoming VNode to update the view.

The overall flow of data to view

So at this point we can draw a conclusion about the overall flow of data to view

  • At the component level, vue performs a new Watcher
  • New Watcher will first have an evaluation operation, which evaluates by performing a function called render, which may compile the template into a render function, generate a VNode (virtual DOM), and apply the Virtual DOM to the view
  • When the Virtual DOM is applied to the view (more on diff later), the expression in the view must be evaluated ({{message}}, we must fetch its value before rendering), and the corresponding getter is triggered to collect the dependency
  • When the data changes, notify goes to the component-level Watcher, which then evaluates to re-collect the dependencies and re-render the view

Let’s take a look at this image from the Vue website again


Virtual DOM

We hid a lot of the details of the Virtual DOM in the last section because the Virtual DOM is so large that it can make us forget what we’re trying to explore. Here we uncover the mystery of the Virtual DOM, which is not so mysterious after all.

Why is there a Virtual DOM?

Those of you who have done front-end performance optimization know that DOM manipulation is slow, and we need to reduce it. Why is it slow? We can try to type a key for a layer of DOM


While working directly with the DOM, you must be careful about operations that can trigger rearrangements.

So what is the Virtual DOM? It is actually a layer of buffer for our code to manipulate DOM, since DOM manipulation is slow, let me manipulate JS objects fast, I will manipulate JS objects, and then finally convert this object together into the real DOM

So it becomes code => Virtual DOM(a special JS object) => DOM

What is the Virtual DOM

What is a virtual DOM? It is a special JS object. What is a Vnode in Vue?

export class VNode {
  constructor( tag? : string, data? : VNodeData, children? :? Array<VNode>, text? : string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? : Function ) {this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.functionalContext = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false}}Copy the code

You can use these attributes to represent a DOM node

Virtual DOM algorithm

Here we are talking about operations involving vm.update above

  • The first is the JS object (Virtual DOM) description tree (vm._render), convert DOM insertion (first render)
  • State changes, generate new JS objects (Virtual DOM), compare the old and new objects
  • Apply the changes to the DOM, save the new JS object (the Virtual DOM), and repeat step 2

$creatElement creates a Virtual DOM from a Vnode (vm._render). $creatElement creates a Virtual DOM from a Vnode (vm._render).

Here we can look specifically at vm._update (which is actually the last two steps of the Virtual DOM algorithm)

  Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')}const prevEl = vm.$el
    const prevVnode = vm._vnode
    // ...
    if(! prevVnode) {// initial render
      // First render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
    } else {
      // updates
      // Update the view
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // ...
  }Copy the code

You can see a key point, vm.__patch__, which is actually the core of the Virtual DOM Diff and the last thing it inserts into the real DOM

Virtual DOM Diff

The complete Virtual DOM Diff algorithm, according to a paper (I forget where), requires O(n^3), because it involves cross-level reuse, such time complexity is unacceptable. Meanwhile, considering that DOM rarely involves cross-level reuse, it is reduced to the current level of reuse. This algorithm is down to O(n), Perfect~

Use a classic React diagram to help you understand the scope of comparison/reuse circled in the same color

Step into the topic, let’s look at the patch function of Vue

function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      // The old node does not exist
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        // If the new node is the same as the old node, patch the old node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        / /... Omit SSR code
        // replacing existing element
        // The new node is the same as the old node
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )
      }
    }
    / /... Omit code
    return vnode.elm
  }Copy the code

So Patch basically does the following things

  • Check whether the old node exists
    • If not, it is the first render and the element is created directly
    • If it exists, sameVnode uses it to determine whether the root nodes are the same
      • If yes, use patchVnode to patch the old node
      • If no, replace the old node with the new node

For the sameVnode judgment, it is a simple comparison of several attribute judgments

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)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}Copy the code

In fact, patchVnode is to compare the child nodes of nodes, and judge the child nodes of the old and new nodes respectively. If there are neither of them or one of them does not exist, it is easier to delete or add them directly. However, if there are both of them, list comparison and some reuse operations will be involved. The method of implementation is Update Dren

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    if (oldVnode === vnode) {
      // The new and old nodes are the same
      return
    }
    / /... Omit code
    if (isUndef(vnode.text)) {
      // If the new node has no text
      if (isDef(oldCh) && isDef(ch)) {
        // If both the old node and the new node have children
        // If not, update the child node
        if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
        // The new node has children, but the old node does not
                // The old node is added
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // The old node has children, but the new node does not
                // Remove the old node
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
        // Old node has text, new node has no text
        nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// If the new node is not equal to the old node text
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }Copy the code

Let’s finally take a look at the updateChildren section which is actually leetcode.com/problems/ed… For the problem of minimum editing distance, the complex dynamic programming algorithm (complexity O(m * n)) is not used to realize the minimum moving operation. Instead, some DOM operations can be sacrificed to optimize part of the scene. The complexity can be reduced to O(Max (m, n)). Then, the first node key (which is often used in V-for) is used to find the same key for patch comparison. If there is no key, it is directly traversed to find similar nodes. If there is no key, the patch will move, and if there is no new node will be created

The list tell us if there might be reuse of nodes, you can use the only key to identify, promote efficiency of patch, but also can’t set key, if not the same, but you set the same, will cause didn’t find the real similar nodes to reuse framework, but reduce the efficiency, will increase the consumption of a create dom

There are many codes here, and readers who are interested can read them in depth. I will not draw diagrams here. Readers can also find corresponding Update Hildren diagrams on the Internet, which will help them understand the patch process

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    constcanMove = ! removeOnlywhile (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        // If the first child of the old node does not exist
        // The old section nods the pointer to move to the next
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        // If the last child of the old node does not exist
        // The last pointer of the old node moves up one
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // If the first of the new node is the same as the first of the old node
        // patch this node and move the old and new nods to the next one respectively
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // If the last of the new node is the same as the last of the old node
        // patch this node and move the tail pointer of the new and old nodes up one respectively
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // If the last of the new node is the same as the first of the old node
        // patch this node and the tail pointer of the new node moves up one, and the nod pointer of the old node moves down one
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // The first node of the new node is the same as the last node of the old node
        // patch this node and the end pointer of the old node moves up one, and the new node moves down one
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // Create a key to index mapping for the old node
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key] // If the first node has a key, find the index of the old node under that key
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // If the new node does not have a key, simply traverse to find the same index
        if (isUndef(idxInOld)) { // New element
          // If index is not found, create a node
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
        } else {
          // If there is index, find the old node that needs to move
          vnodeToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if(process.env.NODE_ENV ! = ='production' && !vnodeToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.')}if (sameVnode(vnodeToMove, newStartVnode)) {
            // start patch if the first node of the old node and the new node are almost the same
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
            // Set the old node to empty
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // Create a new node
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) {
      // If the head pointer of the old node exceeds the tail pointer
      // Indicates that the node is missing
      refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      // If the head pointer of the new node exceeds the tail pointer
      // There are too many nodes
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }Copy the code

conclusion

Here the whole Vue2 principle is explained, there are many details are not in-depth, readers can read the source code to in-depth study. We can review the question at the beginning (in fact, the article is also constantly asking questions to solve the problem), as you see here, I hope you can learn something ~

  • Data response? How do I know about data changes? (Tip: defineProperty)

    One more small detail: how does app.message get messages from Vue Data?

  • How do data changes relate to views? (Hint: Watcher, Dep, Observer)
  • What is the Virtual DOM? What is virtual Dom Diff? (Hint: special JS object)

Reference links/recommended reading

  • In-depth analysis: How to implement a Virtual DOM algorithm
  • Vue source code :compile,link, dependency, batch… Catch all, full analysis!
  • Deep into the responsivity principle

The last

Thanks for reading ~ welcome to follow me haha github.com/BUPT-HJM welcome to continue sightseeing my new blog ~

Welcome to attention