This content notes from Vue is still silicon valley 】 【 source of virtual DOM parsing and diff algorithm of video tutorials, learn from the students good notes blog.csdn.net/wanghuan102…

snabbdom

Introduction to the

  • Snabbdom is a Swedish word that means speed,
  • Snabbdom is a famous virtual DOM library, is the originator of diff algorithm, Vue source code is borrowed from SNabbDOM
  • Official Git: github.com/snabbdom/sn…

The installation

  • The snabbDOM source code on Git is written in TypeScript, and a compiled JavaScript version is not available on Git
  • To use the built JavaScript version of the SNabbDOM library directly, you can download it from NPM: NPM I-S Snabbdom
  • When learning the base of the library, it is recommended that you read the original code, preferably with the library author’s original comments. This will greatly improve your ability to read source code.
  • The version in use is 3.0.1. If you want to maintain the same dependency as the author, install the dependency using YARN

Look at the source tips

Locating folders

Entering the relevant package name directly into vsCode’s explorer will directly locate the current package

When the content is inconsistent

The contents will change when our package version number is different. If you see the source code of some articles on the Internet is different from yours, pay attention to whether the package version is different. You are advised to use YARN to install related dependencies so that the dependencies are consistent

Test environment setup

Start by creating an empty folder

Execute NPM init -y to create a package.json file

Create a new file, webpack.config.js, in the root directory

// https://webpack.docschina.org/
const path = require('path')

module.exports = {
  / / the entry
  entry: './src/index.js'./ / export
  output: {
    // Virtual package path, that is, the folder is not actually generated, but on port 8080 virtual generation, not really physical generation
    publicPath: 'xuni'.// The name of the packaged file
    filename: 'bundle.js'
  },
  devServer: {
    / / the port number
    port: 8080.// Static resources folder
    contentBase: 'www'}}Copy the code

Create the following directory structure

/** src/index.js */
import {
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from "snabbdom";

const patch = init([
  // Init patch function with chosen modules
  classModule, // makes it easy to toggle classes
  propsModule, // for setting properties on DOM elements
  styleModule, // handles styling on elements with support for animations
  eventListenersModule, // attaches event listeners
]);

const container = document.getElementById("container");

const vnode = h("div#container.two.classes", { on: { click: () = > {} } }, [
  h("span", { style: { fontWeight: "bold"}},"This is bold"),
  " and this is just normal text",
  h("a", { props: { href: "/foo"}},"I'll take you places!"),]);// Patch into empty DOM element -- This modifies the DOM as a side effect
patch(container, vnode);

const newVnode = h(
  "div#container.two.classes",
  { on: { click: () = > {} } },
  [
    h(
      "span",
      { style: { fontWeight: "normal".fontStyle: "italic"}},"This is now italic type"
    ),
    " and this is still just normal text",
    h("a", { props: { href: "/bar"}},"I'll take you places!"),]);// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
Copy the code
<! -- www/index.html --> <! DOCTYPE html><html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <button id="btn">Press me to change the DOM</button>
  <div id="container"></div>// It is convenient for patch tree<script src="xuni/bundle.js"></script>
</body>
</html>
</html>
Copy the code

Snabbdom library is DOM library, of course cannot run in nodeJS environment, so we need to build webpack and Webpack-dev-server development environment

package.json

// package.json
{
  "name": "vue_test"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "dev": "webpack-dev-server"
  },
  "keywords": []."author": ""."license": "ISC"."devDependencies": {
    "webpack": "^ 5.36.2"."webpack-cli": "^" 3.3.12."webpack-dev-server": "^ 3.11.2"
  },
  "dependencies": {
    "snabbdom": "^ 3.0.1." "}}Copy the code

Determine success of setup

  • The terminal runs NPM run dev
  • Visit: http://localhost:8080/ and http://127.0.0.1:8080/xuni/bundle.js, you can see the WWW/index. The HTML and xuni/bundle js file content
  • Run the demo program of snabbdom’s official Git homepage in SRC /index.js, which proves that the debugging environment has been set up successfully

Click () => {}

Explains the role of each function in beggar’s virtual DOM and diff

Why it’s called beggar’s Edition:

  1. The h function fixes the number of arguments and how to use them
  2. PatchVnode does not consider the presence of strings or numbers before the node
  3. Components are not taken into account

To sum up, there are many special cases that have not been considered, just the core functions and processes that are normally considered

Virtual DOM correlation

1. H function

The function changes the passed argument to vNone

2. The vnode function

Converts the passed argument to a fixed-format object

The diff related

1. The patch function

2. The createElement method function

Actually creating the node creates the VNode as a DOM

3. PatchVnode function

Is the detailed comparison in the figure above

4. updateChildren

The diff algorithm

  • Four kinds of hit search: ① new before and old before ② new after and old after ③ new after and old before (this hit, involving moving nodes, then the node pointing to the old before, move to the old after) ④ New before and old after (this hit, involving moving nodes, then the node pointing to the old after, move to the old before before)
  • If none of them hit, you need to use a loop to find them

// updateChildren.js
import createElement from './createElement'
import patchVnode from './patchVnode'

// Check whether it is the same virtual node
function checkSameVnode(a, b) {
  return a.sel === b.sel && a.key === b.key
}

export default function updateChildren(parentElm, oldCh, newCh) {
  // console.log(' I am updateChildren')
  // console.log(oldCh, newCh)
  let oldStartIdx = 0 / / the old before
  let newStartIdx = 0 / / new front
  let oldEndIdx = oldCh.length - 1 / / after the old
  let newEndIdx = newCh.length - 1 / / new after
  let oldStartVnode = oldCh[oldStartIdx] // Old former node
  let oldEndVnode = oldCh[oldEndIdx] // Old back node
  let newStartVnode = newCh[newStartIdx] // New front node
  let newEndVnode = newCh[newEndIdx] // New post-node
  let keyMap = null
  // console.log(oldStartIdx, newEndIdx)
  // Start the loop
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    / / the console. The log (' nurture ')
    // The first step is not to judge the hit, but to pass the thing marked with undefined
    if (oldStartVnode === null || oldCh[oldStartIdx] === undefined) {
      oldStartVnode = oldCh[++oldStartIdx]
    } else if (oldEndVnode === null || oldCh[oldEndIdx] === undefined) {
      oldEndVnode = oldCh[--oldEndIdx]
    } else if (newStartVnode === null || newCh[newStartIdx] === undefined) {
      newStartVnode = newCh[++newStartIdx]
    } else if (newEndVnode === null || newCh[newEndIdx] === undefined) {
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldStartVnode, newStartVnode)) {
      // New and old
      console.log('① New and old hit ')
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    } else if (checkSameVnode(oldEndVnode, newEndVnode)) {
      // New queen and old queen
      console.log('② New queen and old queen hit ')
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldStartVnode, newEndVnode)) {
      // New after and old before
      console.log('③ Hit the old queen and the new queen ')
      patchVnode(oldStartVnode, newEndVnode)
      // When ③ new and old hit, move the node. Move the node pointed to by the new node to the node behind the old node
      // How do I move nodes? Whenever you insert a node that is already in the DOM tree, it is moved
      parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
      oldStartVnode = oldCh[++oldStartIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldEndVnode, newStartVnode)) {
      // New before and old after
      console.log('④ New before and old after hit ')
      patchVnode(oldEndVnode, newStartVnode)
      // When ④ new front and old rear hit, the node should be moved. Move the node pointed to by the new node to the node in front of the old node
      parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    } else {
      // None of the four hits were found
      // make keyMap, cache
      if(! keyMap) { keyMap = {}// Start with oldStartIdx and end with oldEndIdx to create keyMap
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
          const key = oldCh[i].key
          if(key ! = =undefined) {
            keyMap[key] = i
          }
        }
      }
      // console.log(keyMap)
      // Find the sequence number of the current item newStartIdx mapped in keyMap
      const idxInOld = keyMap[newStartVnode.key]
      if (idxInOld === undefined) {
        // If idxInOld is undefined, it is completely new
        // The added item (newStartVnode) is not a real DOM
        parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
      } else {
        // If idxInOld is not undefined, it is not new and needs to be moved
        const elmToMove = oldCh[idxInOld]
        patchVnode(elmToMove, newStartVnode)
        // Set this to undefined to indicate that the processing is complete
        oldCh[idxInOld] = undefined
        // Move, call insertBefore
        parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
      }
      // Move the pointer down to move only the new head
      newStartVnode = newCh[++newStartIdx]
    }
  }
  NewStartIdx is still smaller than newEndIdx
  if (newStartIdx <= newEndIdx) {
    // new there are still nodes left unprocessed
    for (let i = newStartIdx; i <= newEndIdx; i++) {
      // insertBefore automatically recognizes null, and if it is null, it automatically goes to the end of the queue. Consistent with appendChild
      // newCh[I] isn't really DOM yet, so createElement needs to be called
      parentElm.insertBefore(createElement(newCh[i]), oldCh[oldStartIdx].elm)
    }
  } else if (oldStartIdx <= oldEndIdx) {
    // old there are still nodes left unprocessed
    // Delete items between oldStartIdx and oldEndIdx in batches
    for (let i = oldStartIdx; i <= oldEndIdx; i++) {
      if (oldCh[i]) {
        parentElm.removeChild(oldCh[i].elm)
      }
    }
  }
}
Copy the code

In the while, if you don’t hit any of them, use a loop to find them

Why just move newStartIdx?
  // Move the pointer down to move only the new head
  newStartVnode = newCh[++newStartIdx]
Copy the code
Why before oldStartvNode.elm?
   parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
Copy the code

The answer to these two questions is the same, because the essence is to turn the old node into the new node

Logic after while

You need to determine which situation causes the while to end

  1. NewStartIdx <= newEndIdx if newStartIdx <= newEndIdx is still valid, the old node needs to be added
  2. If oldStartIdx <= oldEndIdx is still valid, the old node needs to be deleted

Check whether the DOM has been moved

Open F12 in Elements next to the console, change the content of the DOM, and then click the button to change it, or move it if the content is consistent. If the content is back to the original is new.

The source address

Github.com/introvert-y…

The last

Look at the source code to understand why keys are indispensable and cannot be misused in the for loop, allowing us to do some performance optimizations and reduce bugs from the underlying logic. Please indicate the source of reprint, thank you.