We all know that by virtualizing the DOM, you can reduce DOM manipulation and improve page rendering performance

To implement the virtual DOM, there are three main trilogy:

  • compile view to vnode
  • Diff Vnode changes
  • Patch VNode changes to the actual DOM

Imaginary Black fan: “So is this article about delving into the implementation principles and details of the virtual DOM?”

No, no, no, no. Let’s get down to business


Trilogy, diff performance is critical, so the general vnode type and key for comparison, if not consistent, then the Vnode and the following children all off (good, can’t look at (>﹏<)), with a new direct replacement, no longer comparison.

Imaginary black powder: “We all know this, so can this article end here?”

Not yet… Not yet (# ~ ▽ ~ #)

To create a diff, you need two vNodes of the diff, an old vnode and a new vnode.

Imaginary black pink: “Easy, assign old vnode to new vnode”

Uh, like the following?

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = oldVnode
newVnode === oldVnode // true
Copy the code

You can see that the assignment is the same as the old one, so it’s the same object no matter what. Okay

Imaginary black powder: “I want to say clone one, shadow will do” < ( ̄) >

Uh, like the following?

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = Object.assign({}, oldVnode)
oldVnode === newVnode // false
newVnode.a.a1 = 2
oldVnode.a.a1 // 2
Copy the code

The a1 value of the new vnode has been changed, and the a1 value of the old vnode has been changed

Imaginary black powder: “Shadow copy for performance reasons, then deep copy is not possible.”

Uh, like the following?

const _ = require('lodash');

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = _.cloneDeep(oldVnode)
newVnode.a.a1 = 2
oldVnode === newVnode // false
oldVnode.a.a1 === newVnode.a.a1 // false
Copy the code

There seems to be no problem, the functionality is available, but this article is about more efficient diff, and the above solution has two poor performance issues:

  1. Deep copy causes a waste of resources and copies of nodes that have not been updated
  2. All VNodes are traversed each time for comparison, regardless of whether the Vnode has changed

Imaginary black powder: “it seems that you have a better plan. If you have any tricks, come out as soon as possible.”

Then let me slowly, first to the middle line to partition the first (), (), ()


You can be more efficient by avoiding the two performance impacts mentioned above. The corresponding measures are as follows:

  1. Copy on demand: Vnodes that do not change are not copied
  2. On-demand diff: VNodes that do not change do not diff

Assume the vNode data structure and graphical representation is as follows:

let oldVnode = {
    a: {
        a1: { a1_1: 1 },
        a2: { a2_1: 1 }
    },
    b: { b1: { b1_1: 1 } }
}
Copy the code

When changing a1_1 to 2, we want to shadow copy or assign only the following highlighted nodes, which are new vNodes

Therefore, when comparing old vnodes and new vnodes, only the nodes highlighted in the following figure need to be compared

The performance benefit of copy and diff becomes more significant when there are more children under a2 and B nodes

Finally, I present a very simple and crude implementation of this scheme (update method, which considers only objects, not arrays) to better understand this idea at the code level

const assert = require('assert');

let oldVal = {a: {a1: 1}, b: {b1: 2}}

function update(obj, path, val) {
    let fileds = path.split('. ');
    let shadowCopy = targetObj => Object.assign({}, targetObj);
    let result = shadowCopy(obj);
    if (fileds.length > 0) {
        if (fileds.length === 1) {
            result[fileds[0]] = val;
        } else {
            result[fileds[0]] = update(obj[fileds[0]], fileds.length > 1 ? fileds.splice(1).join('. ') : ' ', val)
        }
    }
    return result;
}

const newVal = update(oldVal, 'a.a1', 2);

assert.notStrictEqual(oldVal, newVal);
assert.notStrictEqual(oldVal.a, newVal.a);
assert.notStrictEqual(oldVal.a.a1, newVal.a.a1);
assert.strictEqual(oldVal.b, newVal.b);
Copy the code