【通俗易懂】虛擬DOM,如何更高效DIFF

咱們都知道,經過虛擬DOM,能夠減小DOM操做,提升頁面渲染性能node

要實現虛擬DOM,主要三部曲:數組

  • compile view to vnode
  • diff vnode 的變化
  • patch vnode 變化到實際的DOM

假想的黑粉:"因此這篇文章是要深刻虛擬DOM的實現原理和實現細節嗎?"bash

非也非也,咱們開始愉快地進入正題吧數據結構


三部曲中,diff的性能很關鍵,因此通常對vnode的typekey做比較,若是不一致,則該vnode及如下孩子們所有幹掉(好殘忍,沒法直視(>﹏<)),用新的直接替換,再也不往下對比。性能

假想的黑粉:「這個你們都懂,因此文章至此能夠結束了?」ui

還沒。。。還沒開始(# ̄▽ ̄#)spa

diff,那就得有diff的兩個vnode,一個old vnode,一個new vnode,那new vnode如何產生的呢?code

假想的黑粉:「簡單啊,把old vnode賦值給new vnode」cdn

額~,像如下這樣嗎?對象

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = oldVnode
newVnode === oldVnode // true
複製代碼

能夠看到新舊一致,不管如何賦值都是同個對象,無從對比啦

假想的黑粉:「我是想說clone一個啦,shadow就好了」 <( ̄︶ ̄)>

額~,像如下這樣嗎?

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = Object.assign({}, oldVnode)
oldVnode === newVnode // false
newVnode.a.a1 = 2
oldVnode.a.a1 // 2
複製代碼

能夠看到更改了new vnode的a1值,old vnode的a1值也被改了,也就沒法得知變化了

假想的黑粉:「剛爲了性能考慮說了shadow copy,那實在不行就deep copy吧」

額~,像如下這樣嗎?

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
複製代碼

看上去沒什麼問題,功能是能夠實現了,但這篇文章是要講 高效diff,上面方案有兩個較很差的性能問題:

  1. deep copy形成資源浪費,沒更新的結點也被複制了一份
  2. 每次要遍歷全部vnode進行對比,不管該vnode有沒產生變化

假想的黑粉:「看樣子你是有更好的方案,有什麼花招趕忙使出來吧~」

那就讓我慢慢道來,先來個中橫線分割一下先( ̄︶ ̄)↗


只要避免上面提到的兩點對性能的影響,便可更高效DIFF,對應的措施以下:

  1. 按需copy:沒出現變化的vnode不做copy
  2. 按需diff:沒出現變化的vnode不做diff

假設vnode的數據結構以及圖形表示以下:

let oldVnode = {
    a: {
        a1: { a1_1: 1 },
        a2: { a2_1: 1 }
    },
    b: { b1: { b1_1: 1 } }
}
複製代碼

image.png

當把 a1_1 的值更改成 2 時, 咱們但願只對如下高亮節點進行shadow copy或賦值,如下即爲new vnode

image.png

因此在對比old vnode和new vnode時,只有下圖高亮的節點須要進行比對

image.png

當 a2 和 b 節點下面的子節點越多時,copy 和 diff 所帶來的性能收益就越明顯

最後獻上這種方案的極簡單極粗糙的實現(update方法,只考慮對象,沒考慮數組)以更好的從代碼層面去理解這種思路

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);
複製代碼
相關文章
相關標籤/搜索