摘要:最后刪除新的樹中不存在的節點。而中會記錄對其做了相應的優化,節點的的情況下,不做移動操作。這種情況,在中得到了優化,通過四個指針,在每次循環中先處理特殊情況,并通過縮小指針范圍,獲得性能上的提升。
上篇文章已經介紹過idff的處理邏輯主要分為三塊,處理textNode,element及component,但具體怎么處理component還沒有詳細介紹,接下來講一下preact是如何處理component的。
組件的diff通過學習元素節點的diff操作,我們不妨大膽猜測一下,組件是做了如下diff操作:
組件不同類型或者不存在就創建,走相應的生命周期鉤子
比較組件的屬性
比較組件的孩子
事實上和我們的猜想很相似,在進行下一步之前,我們先了解下preact中的數據結構:
// 如下JSX// App組件的實例,會有以下屬性 { base, // 對應組件渲染的dom _component, // 指向Child組件 } // Child組件有以下屬性 { base, // 與App組件實例指向同一個dom _parentComponent, // 指向App組件 } // 對應的dom節點,即前文中的base對象 { _component // 指向App組件,而不是Child組件 }
然后我們看一下buildComponentFromVNode邏輯:
如果組件類型相同調用setComponentProps
如果組件類型不同:
回收老的組件
創建新的組件實例
調用setComponentProps
回收老的dom
返回dom
function buildComponentFromVNode(dom, vnode, context, mountAll) { let c = dom && dom._component, originalComponent = c, oldDom = dom, isDirectOwner = c && dom._componentConstructor === vnode.nodeName, // 組件類型是否變了 isOwner = isDirectOwner, props = getNodeProps(vnode); while (c && !isOwner && (c = c._parentComponent)) { // 如果組件類型變了,一直向上遍歷;看類型是否相同 isOwner = c.constructor === vnode.nodeName; } // 此時isOwner就代表組件類型是否相同 // 如果組件類型相同,只設置屬性;然后將dom指向c.base if (c && isOwner && (!mountAll || c._component)) { setComponentProps(c, props, 3, context, mountAll); dom = c.base; } else { if (originalComponent && !isDirectOwner) { // 組件類型不同就先卸載組件 unmountComponent(originalComponent); dom = oldDom = null; } // 創建組件的主要邏輯就是return new vnode.nodeName() c = createComponent(vnode.nodeName, props, context); if (dom && !c.nextBase) { c.nextBase = dom; // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: oldDom = null; } setComponentProps(c, props, 1, context, mountAll); dom = c.base; if (oldDom && dom !== oldDom) { oldDom._component = null; recollectNodeTree(oldDom, false); } } return dom; }
可以看到組件進一步diff的核心邏輯在setComponentProps方法中,setComponentProps大致做了兩件事:
調用渲染前的生命周期鉤子: componentWillMount 與 componentWillReceiveProps
調用renderComponent
renderComponent主要邏輯為:
調用shouldComponentUpdate 或 componentWillUpdate生命周期鉤子
調用組件的render方法
如果render的結果是一個組件,做類似與buildComponentFromVNode的操作
如果render的結果是dom節點,調用diff操作
替換新的節點,卸載老的節點或組件
為組件的base添加組件引用_component
調用組件的生命周期鉤子componentDidUpdate,componentDidMount。
至此,我們已經大致了解了preact的大致全流程,接下來我們看一下它的diffChildren的算法:
將原始dom的子節點分為兩部分,有key的放在keyed map里面,沒有key的放在children數組里面。
遍歷vchildren,通過key找到keyed中的child,如果child不存在,從children中取出相同類型的子節點
對child與vchild進行diff,此時得到的dom節點就是新的dom節點
然后與老的dom節點對應的節點比較,操作dom樹。
最后刪除新的dom樹中不存在的節點。
function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) { let originalChildren = dom.childNodes, children = [], keyed = {}, keyedLen = 0, min = 0, len = originalChildren.length, childrenLen = 0, vlen = vchildren ? vchildren.length : 0, j, c, f, vchild, child; if (len !== 0) { for (var i = 0; i < len; i++) { var _child = originalChildren[i], props = _child.__preactattr_, key = vlen && props ? _child._component ? _child._component.__key : props.key : null; if (key != null) { keyedLen++; keyed[key] = _child; } else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) { children[childrenLen++] = _child; } } } // 遍歷虛擬dom節點 // 取child(有key,證明它兩個是要對應比較的) // 如果child和originchildren[i]比較 // originchild沒有,多余,否則插入到originchild前面 if (vlen !== 0) { for (var i = 0; i < vlen; i++) { vchild = vchildren[i]; child = null; // attempt to find a node based on key matching var key = vchild.key; if (key != null) { if (keyedLen && keyed[key] !== undefined) { child = keyed[key]; keyed[key] = undefined; keyedLen--; } } // attempt to pluck a node of the same type from the existing children else if (!child && min < childrenLen) { for (j = min; j < childrenLen; j++) { //從min往后開始遍歷,如果是相同類型的節點就拿出來,那個位置設為undefined if (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) { child = c; children[j] = undefined; if (j === childrenLen - 1) childrenLen--; if (j === min) min++; break; } } } // morph the matched/found/created DOM child to match vchild (deep) child = idiff(child, vchild, context, mountAll); f = originalChildren[i]; if (child && child !== dom && child !== f) { if (f == null) { dom.appendChild(child); } else if (child === f.nextSibling) { removeNode(f); } else { dom.insertBefore(child, f); } } } } // remove unused keyed children: // keyedLen標識老的集合中還有的元素,但沒在新的集合中使用 if (keyedLen) { for (var i in keyed) { if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false); } } // remove orphaned unkeyed children: // min代表拿走的元素 while (min <= childrenLen) { if ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false); } }
從上面可以看出,preact只處理了常見的使用場景,沒有做特別的優化措施,這也導致它在一些情況下的性能比react低,如:從a b到b a。
而react中會記錄lastIndex,對其做了相應的優化,節點的Index > lastIndex的情況下,不做移動操作。
但是如果react中有length > 2,最前面的節點位置與最后面的節點位置互換的情況下,由于index一直小于lastIndex,就會失去上述的優化效果。
這種情況,在snabbdom中得到了優化,snabbdom通過oldStartIdx,oldEndIdx,newStartIdx,newEndIdx四個指針,在每次循環中先處理特殊情況,并通過縮小指針范圍,獲得性能上的提升。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95134.html
摘要:對回收的處理在中,回收調用了兩個方法,節點的回收一般會調用,組件的回收會調用。個人理解從以上源碼閱讀中我們可以看到,最大的性能問題在于遞歸的,中的與也是為了緩解這個問題。為不同類型的更新分配優先級。 對回收的處理 在preact中,回收調用了兩個方法,dom節點的回收一般會調用recollectNodeTree,組件的回收會調用unmountComponent。 preact復用dom...
摘要:是一個最小的庫,但由于其對尺寸的追求,它的很多代碼可讀性比較差,市面上也很少有全面且詳細介紹的文章,本篇文章希望能幫助你學習的源碼。建議與源碼一起閱讀本文。 作為一名前端,我們需要深入學習react的運行機制,但是react源碼量已經相當龐大,從學習的角度,性價比不高,所以學習一個react mini庫是一個深入學習react的一個不錯的方法。 preact是一個最小的react mi...
摘要:的選擇器允許單個線程監視多個輸入通道。一旦執行的線程已經超過讀取代碼中的某個數據片段,該線程就不會在數據中向后移動通常不會。 1、引言 很多初涉網絡編程的程序員,在研究Java NIO(即異步IO)和經典IO(也就是常說的阻塞式IO)的API時,很快就會發現一個問題:我什么時候應該使用經典IO,什么時候應該使用NIO? 在本文中,將嘗試用簡明扼要的文字,闡明Java NIO和經典IO之...
摘要:,,面向切面編程。,切點,切面匹配連接點的點,一般與切點表達式相關,就是切面如何切點。例子中,注解就是切點表達式,匹配對應的連接點,通知,指在切面的某個特定的連接點上執行的動作。,織入,將作用在的過程。因為源碼都是英文寫的。 之前《零基礎帶你看Spring源碼——IOC控制反轉》詳細講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動態代理嗎?看這篇就夠了》介紹了下...
摘要:經過一次冒泡排序,最終在無序表中找到一個最大值,第一次冒泡結束。也是我們后面要說的冒泡排序的優化。冒泡排序只執行第一層循環,而不會執行第二層循環。因此冒泡排序的時間復雜度是。 showImg(https://user-gold-cdn.xitu.io/2019/6/19/16b6f986df6880f9?w=640&h=142&f=gif&s=17175);showImg(https:...
閱讀 1229·2021-11-24 09:39
閱讀 380·2019-08-30 14:12
閱讀 2592·2019-08-30 13:10
閱讀 2434·2019-08-30 12:44
閱讀 959·2019-08-29 16:31
閱讀 846·2019-08-29 13:10
閱讀 2434·2019-08-27 10:57
閱讀 3152·2019-08-26 13:57