https://zhuanlan.zhihu.com/p/...node
React diff 會幫助咱們計算出 Virtual DOM 中真正變化的部分,並只針對該部分進行實際 DOM 操做,而非從新渲染整個頁面,從而保證了每次操做更新後頁面的高效渲染,所以 Virtual DOM 與 diff 是保證 React 性能口碑的幕後推手。算法
計算一棵樹形結構轉換成另外一棵樹形結構的最少操做,是一個複雜且值得研究的問題。傳統 diff 算法經過循環遞歸對節點進行依次對比,效率低下,算法複雜度達到 O(n3),其中 n 是樹中節點的總數。app
對樹進行分層比較,兩棵樹只會對同一層次的節點進行比較。既然 DOM 節點跨層級的移動操做少到能夠忽略不計,針對這一現象,React 經過 updateDepth 對 Virtual DOM 樹進行層級控制,只會對相同顏色方框內的 DOM 節點進行比較,即同一個父節點下的全部子節點。當發現節點已經不存在,則該節點及其子節點會被徹底刪除掉,不會用於進一步的比較。這樣只須要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。dom
因爲 React 只會簡單的考慮同層級節點的位置變換,而對於不一樣層級的節點,只有建立和刪除操做。當出現節點跨層級移動時,並不會出現想象中的移動操做,而是以 A 爲根節點的樹被整個從新建立,這是一種影響 React 性能的操做,所以 React 官方建議不要進行 DOM 節點跨層級的操做。性能
React 是基於組件構建應用的,對於組件間的比較所採起的策略也是簡潔高效。優化
以下圖,當 component D 改變爲 component G 時,即便這兩個 component 結構類似,一旦 React 判斷 D 和 G 是不一樣類型的組件,就不會比較兩者的結構,而是直接刪除 component D,從新建立 component G 以及其子節點。spa
當節點處於同一層級時,React diff 提供了三種節點操做,分別爲:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)。code
容許開發者對同一層級的同組子節點,添加惟一 key 進行區分,雖然只是小小的改動,性能上卻發生了翻天覆地的變化!component
先對新集合的節點進行循環遍歷,for (name in nextChildren),經過惟一 key 能夠判斷新老集合中是否存在相同的節點,if (prevChild === nextChild),若是存在相同節點,則進行移動操做,但在移動前須要將當前節點在老集合中的位置與 lastIndex 進行比較,if (child._mountIndex < lastIndex),則進行節點移動操做,不然不執行該操做。==這是一種順序優化手段,lastIndex 一直在更新,表示訪問過的節點在老集合中最右的位置(即最大的位置)==,若是新集合中當前訪問的節點比 lastIndex 大,說明當前訪問節點在老集合中就比上一個節點位置靠後,則該節點不會影響其餘節點的位置,所以不用添加到差別隊列中,即不執行移動操做,只有當訪問的節點比 lastIndex 小時,才須要進行移動操做。blog
oldVdom
不存在時,將newVdom
生成的dom添加到父元素 newVdom
不存在時,將newVdom
對應index的真實dom刪除 oldVdom, newVdom
的根節點不一致時,直接將oldVdom
替換爲newVdom
若上述都不知足,則說明兩個vdom
的根節點是一致的, 而後遞歸調用 diff & patch
方法
function h(type, props, ...children) { return {type, props, children}; } function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } function changed(node1, node2) { return typeof node1 !== typeof node2 || typeof node1 === 'string' && node1 !== node2 || node1.type !== node2.type } function updateElement($parent, newNode, oldNode, index = 0) { console.log(Array.from(arguments)) // console.log(newNode) // console.log(newNode) if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } else if (changed(newNode, oldNode)) { console.log('if go changed') console.log(newNode, oldNode) $parent.replaceChild( createElement(newNode), $parent.childNodes[index] ); } else if (newNode.type) { console.log('test if go last if') const newLength = newNode.children.length; const oldLength = oldNode.children.length; for (let i = 0; i < newLength || i < oldLength; i++) { updateElement( $parent.childNodes[index], newNode.children[i], oldNode.children[i], i ); } } } // --------------------------------------------------------------------- // let a = ( // <ul> // <li>item 1</li> // <li>item 2</li> // </ul> // ); // // let b = ( // <ul> // <li>item 1</li> // <li>hello!</li> // </ul> // ); let a = h('ul', {}, h('li', {}, 'item1'), h('li', {}, 'item2')) let b = h('ul', {}, h('li', {}, 'item1'), h('li', {}, 'hello!')) const $root = document.getElementById('root'); const $reload = document.getElementById('reload'); updateElement($root, a); $reload.addEventListener('click', () => { updateElement($root, b, a); }); // 4. vdom diffs && patch //index Virtual DOM對應處於真實DOM中的第幾個子節點 function btsPatch(parentDomNode, oldVdom, newVdom, index=0) { if(!oldVdom) parentDomNode.appendChild(applyVDom(newVdom)); if(!newVdom) { if(parentDomNode.childNodes) parentDomNode.removeChild(parentDomNode.childNodes[index]); } if(typeof oldVdom != typeof newVdom || (typeof oldVdom == 'string' && oldVdom != newVdom) || (typeof oldVdom == 'object' && oldVdom.name != newVdom.name) ) { if(parentDomNode.childNodes && parentDomNode.childNodes[index]) parentDomNode.removeChild(parentDomNode.childNodes[index]); parentDomNode.appendChild(applyVDom(newVdom)); } else { if( typeof oldVdom == 'object' ) { let count = Math.max(oldVdom.children.length, newVdom.children.length); if(count > 0) { for(let i=0; i < count; i++) { btsPatch(parentDomNode.childNodes[index], oldVdom.children[i], newVdom.children[i], i); } } } } return // done bts or same string or no children }