Virtual DOM (Virtual DOM)

What is the vdom

  • It’s called the virtual DOM, not the real DOM
  • Use JS to simulate the DOM structure
  • Comparison of DOM variations, done in the JS layer (Turing-complete language)
  • Use VDOM to improve redraw performance

Turing-complete languages: languages that process logic, implement algorithms, use judgments, use recursion, etc

Why vDOM

  • DOM manipulation is expensive and can affect page performance

Example: jquery renders a table and changes the contents of the table


      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Jquery emulates the virtual DOM</title>
</head>

<body>
  <div id="container"></div>
  <button id="change-btn">change</button>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script>
    var data = [{
      name: 'Joe'.age: 20.address: 'Beijing'
    }, {
      name: 'bill'.age: 21.address: 'Shanghai'
    }, {
      name: 'Pockmarked seed'.age: 30.address: 'nanjing'
    }]

    // Render function
    function render(data) {
      var $container = $('#container')
      // Empty the container
      $container.html(' ')

      / / stitching table
      var $table = $('<table>')
      $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'))
      data.forEach(value= > {
        $table.append($('<tr><td>'+value.name+'</td><td>'+value.age+'</td><td>'+value.address+'</td></tr>'))});// Render to the page, and render elements to the page after they have been added to help provide performance
      $container.append($table)
    }

    $('#change-btn').on('click'.function () {
      data[1].age = 23;
      data[2].address = 'shenzhen';
      // Render again
      render(data)
    })

    // First render, executed immediately after the page loads
    render(data)
  </script>
</body>

</html>
Copy the code

When the table is generated according to the data or the content of the table is modified, the container carrying the table will be completely emptied, and then the good table append will be generated again. Such operation has a great loss on the page performance. When making a large project, frequent DOM operation will make the project run more and more card, so the VDOM is derived

How does VDOM work

Snabbdom introduction

In VUe2.0 above, VDOM is implemented based on SNabbDOM

Snabbdom core API

  • H function, simulate vnode, pass three parameters

    1. The type and name of the first argument, DOM
    2. The second parameter, DOM, needs to add the inline style, bound events, and built-in properties
    3. The content of the third argument, DOM
  • Patch function, two parameters, two ways to use

    1. The first is to add elements to a blank DOM
      1. The first parameter is a blank node
      2. The second argument is that the h function generates the element to be added
    2. The second use is to update the content of the existing DOM
      1. The first argument is that the h function generates the old node
      2. The second argument is the newly generated node of the h function, which is used to update the old node

Snabbdom case

Example: Use snabbDOM to rewrite the jQuery rendering table example above


      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>snabbdom</title>
</head>

<body>
  <div id="container"></div>
  <button id="btn-change">change</button>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
  <script>
    var data = [{
      name: 'name'.age: 'age'.address: 'address'
    }, {
      name: 'Joe'.age: 20.address: 'Beijing'
    }, {
      name: 'bill'.age: 21.address: 'Shanghai'
    }, {
      name: 'Pockmarked seed'.age: 30.address: 'nanjing'
    }]
    var snabbdom = window.snabbdom

    / / define the patch
    var patch = snabbdom.init([
      snabbdom_class,
      snabbdom_props,
      snabbdom_style,
      snabbdom_eventlisteners
    ])

    / / define h
    var h = snabbdom.h

    var container = document.getElementById('container')
    var changeBtn = document.getElementById('btn-change')

    var vnode
    function render(data) {
      var newVnode = h('table', {}, data.map(function (item) {
        var tds = []
        var i
        for (i in item) {
          if (item.hasOwnProperty(i)) {
            tds.push(
              h('td', {}, item[i])
            )
          }
        }
        return h('tr', {}, tds)
      }))

      /** * Determine if it is the first time to render the vDOM * if it is the first time to add vNode to the blank element * if it is not the first time to compare the newly generated vnode with the old vnode, modify the changes */
      if (vnode) {
        patch(vnode, newVnode)
      } else {
        patch(container, newVnode)
      }

      // Assign the generated vNode to oldVnode after each rendering
      vnode = newVnode
    }

    changeBtn.addEventListener('click'.function () {
      data[1].age = 30
      data[2].address = 'shenzhen'
      render(data)
    })

    // First render
    render(data)
  </script>
</body>

</html>
Copy the code

Create vDOM with SNabbDOM. When you click modify, compare the old VDOM with the newly generated VDOM and replace different places. The whole large element will not be emptied and re-rendered, but only partial modification, which effectively reduces DOM operations and improves performance

The diff algorithm

The usefulness of the diff

  • Diff command in Linux to see the difference between two text files
  • Git diff compares the differences between two files
  • The diff algorithm in VUE and React is different from that in VDOM

Why is the DIff algorithm used in VDOM

Because DOM manipulation is “expensive”, it is necessary to minimize DOM manipulation and find out which nodes the DOM must update this time. Otherwise, the process of “finding out” requires the DIff algorithm

Diff algorithm in VDOM implementation principle

The patch function in snabbdom.js implements the DIff algorithm

  • patch(dom, vnode)Add the generated VDOM to the blank DOM element

Using simple code to achieve patch(DOM, Vnode), just a simple implementation idea, and can not run directly.

// VNode data model
// vnode = {
// "tag": 'ul',
// "attrs": {"id": 'list'},
// "children": [{
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item1']
/ /}, {
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item2']
/ /}]
// }

function createElement(vnode) {
  var tag = vnode.tag;
  var attrs = vnode.attrs || {};
  var children = vnode.children || [];
  if (tag == null) {
    return null;
  }

  // Create a real DOM element
  var elem = document.createElement(tag);

  / / property
  var attrName;
  for (attrName in attrs) {
    if (attrs.hasOwnProperty(attrName)) {
      // Add attributes to elemelem.setAttribute(attrName, attrs[attrName]); }}/ / child elements
  children.forEach(childVnode= > {
    // Add child elements to elem
    elem.appendChild(createElement(childVnode)); / / recursion
  });

  // Return the actual DOM element
  return elem;
}
Copy the code
  • patch(vnode, newVnode)The newly generated VDOM is compared to the existing VDOM, changing only the changes

Use the following code to achieve a simple element detection of different and replacement logic, just simple logic, you can go to the Internet to find a special diff algorithm

// VNode changes the data model
// vnode = {
// "tag": 'ul',
// "attrs": {"id": 'list'},
// "children": [{
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item1']
/ /}, {
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item2']
/ /}]
// }
// newVnode = {
// "tag": 'ul',
// "attrs": {"id": 'list'},
// "children": [{
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item1']
/ /}, {
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['itemB']
/ /}, {
// "tag": 'li',
// "attrs": { "className": 'item'},
// "children": ['item23]
/ /}]
// }

function updateChildren(vnode, newVnode) {
  var children = vnode.children || [];
  var newChildren = newVnode.children || [];

  children.forEach((childVnode, index) = > {
    var newChildVnode = newChildren[index];
    if (childVnode.tag == newChildVnode.tag) {
      // No changes continue to compare, deep contrast
      updateChildren(childVnode, newChildVnode);
    } else {
      // Change the substitutionreplaceNode(childVnode, newChildVnode); }}); }function replaceNode(vnode, newVnode) {
  var elem = vnode.elem;
  var newElem = createElement(newVnode);

  / / replace
}
Copy the code