What is the virtual DOM

The Virtual DOM is an abstraction of the DOM, essentially a javascript object that describes the DOM more lightly. Shorthand vdom;

Real DOM (real DOM elements are huge because browser standards make DOM complex)

<ul id='list'>
  <li class='item'>itemB</li>
</ul>
Copy the code

Virtual DOM

{tag:'ul', // the element's tag type attrs:{// indicates the attribute id on the element :'list'}, children:[// ul element's children {tag: 'li', attrs:{ className:'item' }, children:['itemA'] } ] }Copy the code

Virtual DOM object parameter analysis:

  • Tag: Specifies the tag type of the element, in this case: ‘ul’ (react type)
  • Attrs: props for specifying attributes on an element, such as id,class, style, and custom attributes.
  • Children: specifies whether the element has child nodes. The argument is passed in as an array, or as a string if it is text

Why do we need the virtual DOM

  • Front-end performance optimization (reduce DOM manipulation). Because frequent DOM manipulation will lead to backflow or redrawing of the browser, this layer of abstraction is needed. In the patch update process, the differences are updated into the DOM as much as possible at one time.
  • Unified patch update without manual DOM operation.
  • Open the door to functional UI programming.
  • Better cross-platform, such as Node.js, does not have DOM. If you want to implement SSR(server rendering), then one way is to use Virtual DOM. Virtual DOM is used in ReactNative, React VR and WEEx.

Disadvantages of the virtual DOM

  • When rendering a large number of DOM for the first time, the extra layer of virtual DOM computation is slower than innerHTML insertion. (An extra DOM copy in memory)
  • This is appropriate if your scene is a virtual DOM with a lot of changes. But with a single, frequent update, the virtual DOM will take more time to compute.

Summary of virtual DOM

  • The virtual DOM is a JS object.
  • 2. DOM manipulation is very expensive.

Use snabbdom.js to see the implementation process of virtual DOM

There are three main processes in the virtual DOM:

  • Compile, how to compile the real DOM into a vNode virtual DOM object.
  • Diff, how do I know the changes between oldVnode and newVnode
  • Patch, how to update changes to the real DOM as a patch. (patch (vnode, newVnode))

The diff algorithm

Diff algorithm is an efficient algorithm for comparing tree nodes of the same layer, avoiding layer by layer search traversal of the tree, so the time complexity is O(n) (the source code before VUE adopts diff first to obtain differences, and then patch the real DOM according to the differences)

Vue’s diff algorithm is modified based on SNabbDOM

React’s Diff Algorithm does the same thing for Vue’s DIff algorithm, i.e. only diff between vnodes of the same class, recursively diff between vnodes of the same class, and finally update the entire DOM tree.

For specific diff process, refer to the article and vue’s patchChild source code below.

function patchChildren(parentElm, oldCh, newCh) { let oldStartIdx = 0; let oldEndIdx = oldCh.length - 1; let oldStartVnode = oldCh[0]; let oldEndVnode = oldCh[oldEndIdx]; let newStartIdx = 0; let newEndIdx = newCh.length - 1; let newStartVnode = newCh[0]; let newEndVnode = newCh[newEndIdx]; let oldKeyToIdx, idxInOld, elmToMove, refElm; while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (! oldStartVnode) { oldStartVnode = oldCh[++oldStartIdx]; } else if (! oldEndVnode) { oldEndVnode = oldCh[--oldEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { newStartVnode); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) {// patchVnode(oldEndVnode, newEndVnode); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) {// patchVnode(oldStartVnode, oldStartVnode); newEndVnode); nodeOps.insertBefore(parentElm, oldStartVnode.elm, OldEndVnode. Elm. NextSibling) / / the old first moves behind the last node oldStartVnode = oldCh [+ + oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) {// patchVnode(oldEndVnode. Elm, oldEndVnode, newStartVnode); nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else {// sameVnode does not match the beginning and end of the sameVnode. Let elmToMove = oldCh[idxInOld]; if (! oldKeyToIdx) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); idxInOld = newStartVnode.key ? oldKeyToIdx[newStartVnode.key] : null; if (! CreateElm (newStartVnode, parentElm) {// If oldCh does not have sameVnode, create nodeops. createElm(newStartVnode, parentElm); newStartVnode = newCh[++newStartIdx]; } else { elmToMove = oldCh[idxInOld]; if (sameVnode(elmToMove, newStartVnode)) { patchVnode(elmToMove, newStartVnode); oldCh[idxInOld] = undefined; nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm); newStartVnode = newCh[++newStartIdx]; } else { nodeOps.createElm(newStartVnode, parentElm); newStartVnode = newCh[++newStartIdx]; } } } } if (oldStartIdx > oldEndIdx) { refElm = (newCh[newEndIdx + 1]) ? newCh[newEndIdx + 1].elm : null; nodeOps.addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx); } else if (newStartIdx > newEndIdx) { nodeOps.removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); }}Copy the code
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

Vue improves diFF performance by:

  • The advantages of giving priority to special scenarios (head/tail, head/tail, etc.) are :(on the one hand, some DOM that do not need to be moved can be processed quickly; on the other hand, fewer nodes to be processed can narrow the processing scope of subsequent operations and improve performance)
  • “In situ reuse,” Vue in judging whether the Pointers point to the same node before and after the update, actually does not require them to refer to the same real DOM node, in fact it only judge to whether similar nodes (such as 2 different div, they are not the same in the DOM, but they belong to the same node), if it is the same node, The Vue then reuses the DOM directly, which has the advantage of not having to move the DOM.

Case: With 1-9 child nodes, change process

  • Nodes with the same head and tail, such as 1 and 10
  • Nodes with the same head and tail: e.g. 2, 9 (after processing nodes with the same head and tail)
  • New node: 11
  • Node to be deleted: 8
  • Other nodes: 3, 4, 5, 6, and 7

[case] cloud.tencent.com/developer/a…

Others: When moving across hierarchies, delete and create operations are performed instead of simply moving. Case study:When the root node finds that A has disappeared in the child node, it destroys A directly. When D discovers that A child node A is added, it creates A new child node (including the child node) as its child node. Diff: create A -> create B -> create C -> delete A

conclusion

  • Ps: Vue maintains the data as observable data. Each item of the data is collected through the getter for dependency collection, and then the dependency is converted into watcher and stored in the closure. After data modification, setter methods of the data are triggered, and then all watcher methods are called to modify the old virtual DOM, thus generating a new virtual DOM. Then, the diff algorithm is used to get the difference between the old and new DOM, and the real DOM is updated according to the difference.

The implementation process of the whole virtual DOM:

  • Emulate the DOM with JavaScript objects
  • Convert this virtual DOM into a real DOM and insert it into the page
  • If an event is modified, a new virtual DOM needs to be generated
  • Compare the differences between two virtual DOM trees to obtain difference objects (also known as patches)
  • Apply the difference object to a real DOM tree

Now you know what to watch out for

  • Try not to modify the DOM across layers
  • When developing components, maintaining a stable DOM structure can help improve performance
  • Setting the key makes diff more efficient

Interview to talk about diff algorithm

The time complexity of the traditional diff algorithm is O(n^3), which is unacceptable in front-end render. In order to reduce the time complexity, the React diff algorithm made some compromises, abandoning the optimal solution and eventually reducing the time complexity to O(n). For reference:

  • 1. Tree Diff: Only compare DOM nodes of the same layer, ignoring the movement of DOM nodes across different layers

React only compares DOM nodes in the same color box, that is, all child nodes under the same parent node. When a node is found to be nonexistent, the node and its children are completely removed and not used for further comparison.

This allows you to compare the entire DOM tree with only one walk through the tree. This means that if a DOM node is moved across hierarchies, React will delete the old node and generate new ones without reuse.

  • Component diff: If the component is not of the same type, the old component is removed and a new component is created.
  • 3. Element diff: For a group of child nodes at the same level, unique ids are required to distinguish them

If there is no ID to differentiate, any insertion will cause the list after the insertion location to be completely re-rendered. This is why you use unique keys when rendering lists.

snabbdom.js

virtual-dom

Github.com/aooy/blog/i… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…