Rendering the real DOM is expensive, and DOM manipulation causes rearrangements and redraws in the browser, which is called browser rerendering. Browser re-rendering is very performance intensive because the entire page has to be redrawn. When data changes, especially when large amounts of data change, such as in a list, manipulating the DOM directly causes the browser to rerender the entire list. The core of DIff in the virtual DOM is to use javascript objects to describe the real DOM instead of directly manipulating the DOM when the data changes. When the data changes, the javascript object is first compared to see if it has changed, to find the location of all the changes, and finally to minimize the update of the changed location to improve performance.

Virtual DOM

The Virtual DOM(Virtual DOM) is a common JS object to describe the DOM object.

  • The virtual DOM maintains the state of the program, tracking the last state
  • Update the real DOM by comparing the two state differences

Functions of the virtual DOM

  • Maintaining the relationship between view and state allows you to save the state of the view
  • Improved rendering performance in complex view situations
  • cross-platform
    • The browser platform renders the DOM
    • Server render SSR(nuxt.js/nex.js)
    • Native apps (Weex/React Native)
    • Small program (MPvue/UNI-app), etc

Virtual DOM library

Snabbdom

  • The virtual DOM used internally in vue.js 2.x is the modified Snabbdom
  • About 200 SLOC (Single line of code)
  • Extensible through modules
  • Source code is developed in TypeScript
  • One of the fastest Virtual DOM

Snabbdom

Snabbdom basic use

  • Create a project
  1. Install the parcel

  1. The configuration scripts

  • Create index. HTML
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>snabbdom-demo</title>
</head>
<body>
  <div id='app'></div>
  <script src="./src/basicusage.js"></script>
</body>
</html>
Copy the code
  • Import Snabbdom
  1. Install Snabbdom

    npm intall snabbdom

  2. Import Snabbdom

    Snabbdom has two core functions

    • The init () and h
      • Init () is a higher-order function that returns patch()
      • H () Returns the virtual node VNode

Case 1:

// basicusage.js

Parcel /webpack 4 does not support the exports field in package.json
import { init } from 'snabbdom/build/init'
import { h } from 'snabbdom/build/h'

const patch = init([])

// First parameter: tag + selector
// The second argument is the text in the tag if it is a string
let vnode = h('div#container.cls'.'Hello World')
let app = document.querySelector('#app')
// The first argument: the old VNode, which can be a DOM element
// Second argument: new VNode
// Return the new VNode
let oldVnode = patch(app, vnode)
patch(oldVnode, vnode)
Copy the code

Case 2:

// basicusage.js

import { init } from 'snabbdom/build/init'
import { h } from 'snabbdom/build/h'

const patch = init([])
// Use h() to create a div. You can create child elements within the div
let vnode = h('div#container', [
  h('h1'.'Hello Snabbdom'),
  h('p'.This is a p prime.)])let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)

setTimeout(() = > {
  // vnode = h('div#container', [
  // h('h1', 'Hello World'),
  // h('p', 'Hello P')
  // ])
  // patch(oldVnode, vnode)

  // Clear the contents of div
  patch(oldVnode, h('! '))},2000);
Copy the code
  • Modules in Snabbdom

Function of modules

  1. Snabbdom’s core library does not handle the properties/styles/events of DOM elements. It can be implemented by registering the modules provided by Snabbdom by default
  2. Modules in Snabbdom can be used to extend the functionality of Snabbdom
  3. Snabbdom modules are implemented by registering global hook functions

Official provided module

  1. attributes
  2. props
  3. dataset
  4. class
  5. style
  6. eventlisteners

Module Usage Steps

  1. Import the required modules
  2. Registers modules in init()
  3. Use the module as the second argument to the h() function
// modules.js

import { init } from 'snabbdom/build/init'
import { h } from 'snabbdom/build/h'

// 1. Import the module
import { styleModule } from 'snabbdom/build/modules/style'
import { eventListenersModule } from 'snabbdom/build/modules/eventlisteners'

// 2. Register the module
const patch = init([
  styleModule,
  eventListenersModule
])

// 3. Use the second argument of the h() function to pass the data (object) used in the module.
let vnode = h('div', [
  h('h1', { style: { backgroundColor: 'red'}},'Hello World'),
  h('p', { on: { click: eventHandler } }, 'Hello P')])function eventHandler () {
  console.log('Don't touch me. It hurts.')}let app = document.querySelector('#app')
patch(app, vnode)
Copy the code

Snabbdom source code parsing

The core of Snabbdom

  • Init () sets the module and creates the patch() function
  • Use the h() function to create a JavaScript object (VNode) that describes the real DOM
  • Patch () Compares the old and new Vnodes
  • Update the changes to the real DOM tree

Snabbdom source address github.com/snabbdom/sn…

The diff algorithm

The essence of the Diff algorithm is to find the difference between two objects with the aim of reusing as many nodes as possible. This object corresponds to the virtual DOM in Vue, which uses javascript objects to represent the DOM structure on the page. Virtual DOM is to extract the real DOM data and simulate the tree structure in the form of objects. What diff algorithm compares is virtual DOM.

Diff algorithm is to compare the same node of the DOM tree before and after the operation, compare layer by layer, and then insert it into the real DOM for rendering. It will add a unique identifier to the list of loops. Because the VUE component is highly reusable, the key is added to identify the uniqueness of the component, so that the DIFF algorithm can correctly identify this node and find the correct location to insert a new node.

Diff algorithm execution process

Diff is to find the child nodes of the same level in order to compare, and then find the next level of node comparison.

  • When comparing nodes of the same level, the mark index is first set on the beginning and end nodes of the new and old node array, and the index is moved during the traversal.

The index is marked as:

OldStartIdx/newStartIdx (old start index/new start index) oldEndIdx/ newEndIdx (old end index/new end index)

The corresponding nodes are:

OldStartVnode/newStartVnode (old start/new start node) oldEndVnode/newEndVnode (old end node/newEnd node)

  • The comparison of the beginning and end points takes place in the following sequence

    • ifOldStartVnode and newStartVnode are sameVnode (same key and sel), the callpatchVnode()Compare and update nodes, move the old start and new start indexes back,oldStartIdx++ / newStartIdx++To the next loop. If not, proceed to the next judgment.
    • ifOldEndVnode and newEndVnode are sameVnode (same key and sel), the callpatchVnode()Compare and update nodes, moving the old and new endpoints forwardoldEndIdx-- / newEndIdx--To enter the next cycle; If not, proceed to the next judgment.
    • ifOldStartVnode and newEndVnode are sameVnode (same key and sel), i.e.,Old start node/new end nodeSame thing, callpatchVnode()Compare and update nodes, move the DOM element corresponding to oldStartVnode after the DOM element corresponding to oldEndVnode of the current tag, and then update the indexoldStartIdx++ / newEndIdx--To enter the next cycle; If not, go to the next judgment.
    • ifOldEndVnode and newStartVnode are sameVnode (same key and sel), i.e.,Old end node/new start nodeSame thing, callpatchVnode()Compare and update nodes, move the DOM element corresponding to oldEndVnode before the DOM element corresponding to oldStartVnode of the current tag, and then update the indexoldEndIdx-- / newStartIdx++To enter the next cycle; If not, go to the next step.
  • If both the first and last marked nodes fail to pass the comparison, go to the following steps:

    • Finds the same node in the old node array using the key of the currently tagged newStartVnode.
    • If newStartVnode is not found, newStartVnode is a new node, then newStartVnode creates a new DOM element, insert newStartIdx++ before the corresponding DOM element of oldStartVnode with the current tag, and enter the next loop.
    • If so, determine whether the new node has the same SEL selector as the old node found.
      • If so, call patchVnode() to compare and update nodes, move the DOM element corresponding to the old node to the front of the DOM element corresponding to the oldStartVnode, newStartIdx++, and enter the next loop.
      • If not, the node is modified, then newStartVnode is used to create a new DOM element and insert newStartIdx++ before the corresponding DOM element of oldStartVnode with the current tag to enter the next loop.
  • At the end of the peer comparison loop, all children of the old node have been traversed first (oldStartIdx > oldEndIdx), and all children of the new node have been traversed first (newStartIdx > newEndIdx). In this case, the new node array needs to be processed:

    • ifThe old nodeThe array is traversed first (oldStartIdx > oldEndIdx), indicating that the new node has surplus and is a newly created Vnode, then use these remaining nodes to create new DOM elements and batch insert them into the Vnode after the currently marked newEndVnode (that is, the identifier index isnewEndIdx+1If the Vnode does not exist before the DOM element, it is inserted at the end.
    • ifThe new nodeThe array is traversed first (newStartIdx > newEndIdx), indicating that there are redundant old nodes, this will directly delete the redundant nodes in batches.