咱們都知道,經過虛擬DOM,能夠減小DOM操做,提升頁面渲染性能node
要實現虛擬DOM,主要三部曲:數組
假想的黑粉:"因此這篇文章是要深刻虛擬DOM的實現原理和實現細節嗎?"bash
非也非也,咱們開始愉快地進入正題吧數據結構
三部曲中,diff的性能很關鍵,因此通常對vnode的type和key做比較,若是不一致,則該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,上面方案有兩個較很差的性能問題:
假想的黑粉:「看樣子你是有更好的方案,有什麼花招趕忙使出來吧~」
那就讓我慢慢道來,先來個中橫線分割一下先( ̄︶ ̄)↗
只要避免上面提到的兩點對性能的影響,便可更高效DIFF,對應的措施以下:
假設vnode的數據結構以及圖形表示以下:
let oldVnode = {
a: {
a1: { a1_1: 1 },
a2: { a2_1: 1 }
},
b: { b1: { b1_1: 1 } }
}
複製代碼
當把 a1_1 的值更改成 2 時, 咱們但願只對如下高亮節點進行shadow copy或賦值,如下即爲new vnode
因此在對比old vnode和new vnode時,只有下圖高亮的節點須要進行比對
當 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);
複製代碼