摘要:最后刪除新的樹中不存在的節(jié)點。而中會記錄對其做了相應(yīng)的優(yōu)化,節(jié)點的的情況下,不做移動操作。這種情況,在中得到了優(yōu)化,通過四個指針,在每次循環(huán)中先處理特殊情況,并通過縮小指針范圍,獲得性能上的提升。
上篇文章已經(jīng)介紹過idff的處理邏輯主要分為三塊,處理textNode,element及component,但具體怎么處理component還沒有詳細(xì)介紹,接下來講一下preact是如何處理component的。
組件的diff通過學(xué)習(xí)元素節(jié)點的diff操作,我們不妨大膽猜測一下,組件是做了如下diff操作:
組件不同類型或者不存在就創(chuàng)建,走相應(yīng)的生命周期鉤子
比較組件的屬性
比較組件的孩子
事實上和我們的猜想很相似,在進(jìn)行下一步之前,我們先了解下preact中的數(shù)據(jù)結(jié)構(gòu):
// 如下JSX// App組件的實例,會有以下屬性 { base, // 對應(yīng)組件渲染的dom _component, // 指向Child組件 } // Child組件有以下屬性 { base, // 與App組件實例指向同一個dom _parentComponent, // 指向App組件 } // 對應(yīng)的dom節(jié)點,即前文中的base對象 { _component // 指向App組件,而不是Child組件 }
然后我們看一下buildComponentFromVNode邏輯:
如果組件類型相同調(diào)用setComponentProps
如果組件類型不同:
回收老的組件
創(chuàng)建新的組件實例
調(diào)用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就代表組件類型是否相同 // 如果組件類型相同,只設(shè)置屬性;然后將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; } // 創(chuàng)建組件的主要邏輯就是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; }
可以看到組件進(jìn)一步diff的核心邏輯在setComponentProps方法中,setComponentProps大致做了兩件事:
調(diào)用渲染前的生命周期鉤子: componentWillMount 與 componentWillReceiveProps
調(diào)用renderComponent
renderComponent主要邏輯為:
調(diào)用shouldComponentUpdate 或 componentWillUpdate生命周期鉤子
調(diào)用組件的render方法
如果render的結(jié)果是一個組件,做類似與buildComponentFromVNode的操作
如果render的結(jié)果是dom節(jié)點,調(diào)用diff操作
替換新的節(jié)點,卸載老的節(jié)點或組件
為組件的base添加組件引用_component
調(diào)用組件的生命周期鉤子componentDidUpdate,componentDidMount。
至此,我們已經(jīng)大致了解了preact的大致全流程,接下來我們看一下它的diffChildren的算法:
將原始dom的子節(jié)點分為兩部分,有key的放在keyed map里面,沒有key的放在children數(shù)組里面。
遍歷vchildren,通過key找到keyed中的child,如果child不存在,從children中取出相同類型的子節(jié)點
對child與vchild進(jìn)行diff,此時得到的dom節(jié)點就是新的dom節(jié)點
然后與老的dom節(jié)點對應(yīng)的節(jié)點比較,操作dom樹。
最后刪除新的dom樹中不存在的節(jié)點。
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節(jié)點 // 取child(有key,證明它兩個是要對應(yīng)比較的) // 如果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往后開始遍歷,如果是相同類型的節(jié)點就拿出來,那個位置設(shè)為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標(biāo)識老的集合中還有的元素,但沒在新的集合中使用 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只處理了常見的使用場景,沒有做特別的優(yōu)化措施,這也導(dǎo)致它在一些情況下的性能比react低,如:從a b到b a。
而react中會記錄lastIndex,對其做了相應(yīng)的優(yōu)化,節(jié)點的Index > lastIndex的情況下,不做移動操作。
但是如果react中有l(wèi)ength > 2,最前面的節(jié)點位置與最后面的節(jié)點位置互換的情況下,由于index一直小于lastIndex,就會失去上述的優(yōu)化效果。
這種情況,在snabbdom中得到了優(yōu)化,snabbdom通過oldStartIdx,oldEndIdx,newStartIdx,newEndIdx四個指針,在每次循環(huán)中先處理特殊情況,并通過縮小指針范圍,獲得性能上的提升。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95134.html
摘要:對回收的處理在中,回收調(diào)用了兩個方法,節(jié)點的回收一般會調(diào)用,組件的回收會調(diào)用。個人理解從以上源碼閱讀中我們可以看到,最大的性能問題在于遞歸的,中的與也是為了緩解這個問題。為不同類型的更新分配優(yōu)先級。 對回收的處理 在preact中,回收調(diào)用了兩個方法,dom節(jié)點的回收一般會調(diào)用recollectNodeTree,組件的回收會調(diào)用unmountComponent。 preact復(fù)用dom...
摘要:是一個最小的庫,但由于其對尺寸的追求,它的很多代碼可讀性比較差,市面上也很少有全面且詳細(xì)介紹的文章,本篇文章希望能幫助你學(xué)習(xí)的源碼。建議與源碼一起閱讀本文。 作為一名前端,我們需要深入學(xué)習(xí)react的運(yùn)行機(jī)制,但是react源碼量已經(jīng)相當(dāng)龐大,從學(xué)習(xí)的角度,性價比不高,所以學(xué)習(xí)一個react mini庫是一個深入學(xué)習(xí)react的一個不錯的方法。 preact是一個最小的react mi...
摘要:的選擇器允許單個線程監(jiān)視多個輸入通道。一旦執(zhí)行的線程已經(jīng)超過讀取代碼中的某個數(shù)據(jù)片段,該線程就不會在數(shù)據(jù)中向后移動通常不會。 1、引言 很多初涉網(wǎng)絡(luò)編程的程序員,在研究Java NIO(即異步IO)和經(jīng)典IO(也就是常說的阻塞式IO)的API時,很快就會發(fā)現(xiàn)一個問題:我什么時候應(yīng)該使用經(jīng)典IO,什么時候應(yīng)該使用NIO? 在本文中,將嘗試用簡明扼要的文字,闡明Java NIO和經(jīng)典IO之...
摘要:,,面向切面編程。,切點,切面匹配連接點的點,一般與切點表達(dá)式相關(guān),就是切面如何切點。例子中,注解就是切點表達(dá)式,匹配對應(yīng)的連接點,通知,指在切面的某個特定的連接點上執(zhí)行的動作。,織入,將作用在的過程。因為源碼都是英文寫的。 之前《零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)》詳細(xì)講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動態(tài)代理嗎?看這篇就夠了》介紹了下...
摘要:經(jīng)過一次冒泡排序,最終在無序表中找到一個最大值,第一次冒泡結(jié)束。也是我們后面要說的冒泡排序的優(yōu)化。冒泡排序只執(zhí)行第一層循環(huán),而不會執(zhí)行第二層循環(huán)。因此冒泡排序的時間復(fù)雜度是。 showImg(https://user-gold-cdn.xitu.io/2019/6/19/16b6f986df6880f9?w=640&h=142&f=gif&s=17175);showImg(https:...
閱讀 1253·2021-11-24 09:39
閱讀 391·2019-08-30 14:12
閱讀 2605·2019-08-30 13:10
閱讀 2449·2019-08-30 12:44
閱讀 974·2019-08-29 16:31
閱讀 860·2019-08-29 13:10
閱讀 2455·2019-08-27 10:57
閱讀 3169·2019-08-26 13:57