由于源碼中diff算法摻雜了太多別的功能模塊刻蚯,并且dom diff相對于之前的代碼實(shí)現(xiàn)來說還是有些麻煩的,尤其是列表對比的算法退唠,所以這里我們單獨(dú)拿出來說他實(shí)現(xiàn)
前言
眾所周知脊串,React中最為人稱贊的就是Virtual DOM和 diff 算法的完美結(jié)合,讓我們可以不顧性能的“任性”更新界面埋同,前面文章中我們有介紹道Virtual DOM,其實(shí)就是通過js來模擬dom的實(shí)現(xiàn)棵红,然后通過對js obj的操作凶赁,最后渲染到頁面中,但是逆甜,如果當(dāng)我們修改了一丟丟東西虱肄,就要渲染整個頁面的話,性能消耗還是非常大的交煞,如何才能準(zhǔn)確的修改該修改的地方就是我們diff算法的功能了浩峡。
其實(shí)所謂的diff算法大概就是當(dāng)狀態(tài)發(fā)生改變的時候,重新構(gòu)造一個新的Virtual DOM错敢,然后根據(jù)與老的Virtual DOM對比,生成patches補(bǔ)丁,打到對應(yīng)的需要修改的地方稚茅。
這里引用司徒正美的介紹
最開始經(jīng)典的深度優(yōu)先遍歷DFS算法纸淮,其復(fù)雜度為O(n^3),存在高昂的diff成本亚享,然后是cito.js的橫空出世咽块,它對今后所有虛擬DOM的算法都有重大影響。它采用兩端同時進(jìn)行比較的算法欺税,將diff速度拉高到幾個層次侈沪。緊隨其后的是kivi.js,在cito.js的基出提出兩項(xiàng)優(yōu)化方案晚凿,使用key實(shí)現(xiàn)移動追蹤及基于key的編輯長度距離算法應(yīng)用(算法復(fù)雜度 為O(n^2))亭罪。但這樣的diff算法太過復(fù)雜了,于是后來者snabbdom將kivi.js進(jìn)行簡化歼秽,去掉編輯長度距離算法应役,調(diào)整兩端比較算法。速度略有損失燥筷,但可讀性大大提高箩祥。再之后,就是著名的vue2.0 把snabbdom整個庫整合掉了肆氓。
與傳統(tǒng)diff對比
傳統(tǒng)的diff算法通過循環(huán)遞歸每一個節(jié)點(diǎn)袍祖,進(jìn)行對比,這樣的操作效率非常的低谢揪,復(fù)雜程度O(n3),其中n標(biāo)識樹的節(jié)點(diǎn)總數(shù)蕉陋。如果React僅僅是引入傳統(tǒng)的diff算法的話,其實(shí)性能也是非常差的键耕。然而FB通過大膽的策略寺滚,滿足了大多數(shù)的性能最大化,將O(n3)復(fù)雜度的問題成功的轉(zhuǎn)換成了O(n),并且后面對于同級節(jié)點(diǎn)移動屈雄,犧牲一定的DOM操作村视,算法的復(fù)雜度也才打到O(max(M,N))。
實(shí)現(xiàn)思路
這里借用下網(wǎng)上的一張圖酒奶,感覺畫的非常贊~
大概解釋下:
額蚁孔。。惋嚎。其實(shí)上面也已近解釋了杠氢,當(dāng)Virtual DOM發(fā)生變化的時,如上圖的第二個和第三個 p 的sonx被刪除了另伍,這時候鼻百,我們就通過diff算法绞旅,計算出前后Virtual DOM的差異->補(bǔ)丁對象patches,然后根據(jù)這個patches對象中的信息來遍歷之前的老Virtual DOM樹温艇,對其需要更新的地方進(jìn)行更新因悲,使其變成新VIrtual DOM。
diff 策略
Web UI中節(jié)點(diǎn)跨級操作特別少勺爱,可以忽略不計
擁有相同類的兩個組件將會生成相似的樹形結(jié)構(gòu)晃琳,擁有不同類的兩個組件將會生成不同的樹形結(jié)構(gòu)。(哪怕一樣的而我也認(rèn)為不一樣 -> 大概率優(yōu)化)
對于同一層級的一組子節(jié)點(diǎn)琐鲁,他們可以通過唯一的key來區(qū)分卫旱,以方便后續(xù)的列表對比算法
基于如上,React分別對tree diff围段、Component diff 顾翼、element diff 進(jìn)行了算法優(yōu)化。
tree diff
基于策略一蒜撮,React的diff非常簡單明了:只會對同一層次的節(jié)點(diǎn)進(jìn)行比較暴构。這種非傳統(tǒng)的按深度遍歷搜索,這種通過大膽假設(shè)得到的改進(jìn)方案段磨,不僅符合實(shí)際場景的需要取逾,而且大幅降低了算法實(shí)現(xiàn)復(fù)雜度,從O(n^3)提升至O(n)苹支。
基于此砾隅,React官方并不推薦進(jìn)行DOM節(jié)點(diǎn)的跨層級操作 ,倘若真的出現(xiàn)了债蜜,那就是非常消耗性能的remove和create的操作了晴埂。
我是真的不會畫圖
Component diff
由于React是基于組件開發(fā)的,所以組件的dom diff其實(shí)也非常簡單寻定,如果組件是同一類型儒洛,則進(jìn)行tree diff比較。如果不是狼速,則直接放入到patches中琅锻。即使是子組件結(jié)構(gòu)類型都相同,只要父組件類型不同向胡,都會被重新渲染恼蓬。這也說明了為什么我們推薦使用shouldComponentUpdate來提高React性能。
大概的感覺是醬紫的
list diff
對于節(jié)點(diǎn)的比較僵芹,其實(shí)只有三種操作处硬,插入、移動和刪除拇派。(這里最麻煩的是移動荷辕,后面會介紹實(shí)現(xiàn))凿跳。當(dāng)被diff節(jié)點(diǎn)處于同一層級時,通過三種節(jié)點(diǎn)操作新舊節(jié)點(diǎn)進(jìn)行更新:插入疮方,移動和刪除拄显,同時提供給用戶設(shè)置key屬性的方式調(diào)整diff更新中默認(rèn)的排序方式,在沒有key值的列表diff中案站,只能通過按順序進(jìn)行每個元素的對比,更新棘街,插入與刪除蟆盐,在數(shù)據(jù)量較大的情況下,diff效率低下遭殉,如果能夠基于設(shè)置key標(biāo)識盡心diff石挂,就能夠快速識別新舊列表之間的變化內(nèi)容,提升diff效率险污。
對于這三種理論知識可以參照知乎上不可思議的 react diff的介紹痹愚。
算法實(shí)現(xiàn)
前方高清多碼預(yù)警
diff
這里引入代碼處理我們先撇開list diff中的移動操作,先一步一步去實(shí)現(xiàn)
根據(jù)節(jié)點(diǎn)變更類型蛔糯,我們定義如下幾種變化
const ATTRS = 'ATTRS';//屬性改變
const TEXT = 'TEXT';//文本改變
const REMOVE = 'REMOVE';//移除操作
const REPLACE = 'REPLACE';//替換操作
let Index = 0;
解釋下index拯腮,為了方便演示diff,我們暫時沒有想react源碼中給每一個Element添加唯一標(biāo)識
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,//重點(diǎn)在這里
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
return element;
};
...
'use strict';
// The Symbol used to tag the ReactElement type. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
module.exports = REACT_ELEMENT_TYPE;
我們遍歷每一個VDom蚁飒,以index為索引动壤。注意這里我們使用全局變量index,因?yàn)楸闅v整個VDom淮逻,以index作為區(qū)分琼懊,所以必須用全局變量,當(dāng)然爬早,GitHub上有大神的實(shí)現(xiàn)方式為{index:0}
,哈引用類型傳遞哼丈,換湯不換藥
開始遍歷
export default function diff(oldTree, newTree) {
let patches = {};
// 遞歸樹, 比較后的結(jié)果放到補(bǔ)丁包中
walk(oldTree, newTree, Index, patches)
return patches;
}
function walk(oldNode, newNode, index, patches) {
let currentPatch = [];
if(!newNode){
currentPatch.push({
type:REMOVE,
index
});
}else if(isString(oldNode) && isString(newNode)){
if(oldNode !== newNode){// 判斷是否為文本
currentPatch.push({
type:TEXT,
text:newNode
});
}
}else if (oldNode.type === newNOde.type) {
// 比較屬性是否有更改
let attrs = diffAttr(oldNode.porps, newNode.props);
if (Object.keys(attrs).length > 0) {
currentPatch.push({
type: ATTRS,
attrs
});
}
// 比較兒子們
diffChildren(oldNode.children,newNode.children,patches);
}else{
// 說明節(jié)點(diǎn)被替換
currentPatch.push({
type: REPLACE,
newNode
});
}
currentPatch.length ? patches[index] = currentPatch : null;
}
function diffChildren(oldChildren,newChildren,patches) {
oldChildren.forEach((child,ids)=>{
// index 每次傳遞給walk時筛严, index應(yīng)該是遞增的.所有的都基于同一個Index
walk(child,newChildren[idx],++Index,patches);
})
}
function diffAttr(oldAttrs, newAttrs) {
let patch = {};
// 判斷老屬性和新屬性的關(guān)系
for (let key in oldAttrs) {
if (oldAttrs[key] !== newAttrs[key]) {
patch[key] = newAttrs[key]; //有可能是undefined => 新節(jié)點(diǎn)中刪了該屬性
}
}
// 新節(jié)點(diǎn)新增了很多屬性
for (let key in newAttrs) {
if (!oldAttrs.hasOwnProperty(key)) {
patch[key] = newAttrs[key];
}
}
return patch;
}
在diff過程中醉旦,我們需要去判斷文本標(biāo)簽,需要在util中寫一個工具函數(shù)
function isString(node) {
return Object.prototype.toString.call(node)==='[object String]';
}
實(shí)現(xiàn)思路非常簡單脑漫,手工流程圖了解下
通過diff后髓抑,最終我們會拿到新舊VDom的patches補(bǔ)丁,補(bǔ)丁的內(nèi)容大致如下:
patches = {
1:{
type:'REMOVE',
index:1
},
3:{
type:'TEXT',
newText:'hello Nealyang~',
},
6:{
type:'REPLACE',
newNode:newNode
}
}
大致是這么個感覺优幸,兩秒鐘體會下~
這里應(yīng)該會有點(diǎn)詫異的是1 3 6...
是什么鬼吨拍?
因?yàn)橹拔覀冋f過,diff采用的依舊是深度優(yōu)先遍歷网杆,及時你是改良后的升級產(chǎn)品羹饰,但是遍歷流程依舊是:
patches
既然patches補(bǔ)丁已經(jīng)拿到了伊滋,該如何使用呢,對队秩,我們依舊是遍歷笑旺!
Element 調(diào)用render后,我們已經(jīng)可以拿到一個通過VDom(代碼)解析后的真是Dom了馍资,所以我們只需要將遍歷真實(shí)DOM筒主,然后在指定位置修改對應(yīng)的補(bǔ)丁上指定位置的更改就行了。
代碼如下:(自己實(shí)現(xiàn)的簡易版)
let allPaches = {};
let index = 0; //默認(rèn)哪個需要補(bǔ)丁
export default function patch(dom, patches) {
allPaches = patches;
walk(dom);
}
function walk(dom) {
let currentPatche = allPaches[index];
let childNodes = dom.childNodes;
childNodes.forEach(element => walk(element));
if (currentPatche > 0) {
doPatch(dom, currentPatche);
}
}
function doPatch(node, patches) {
patches.forEach(patch => {
switch (patch.type) {
case 'ATTRS':
setAttrs(patch.attrs)//別的文件方法
break;
case 'TEXT':
node.textContent = patch.text;
break;
case 'REPLACE':
let newNode = patch.newNode instanceof Element ? render(patch.newNode) : document.createTextNode(patch.newNode);
node.parentNode.replaceChild(newNode, node)
break;
case 'REMOVE':
node.parentNode.removeChild(node);
break;
}
})
}
關(guān)于setAttrs其實(shí)功能都加都明白鸟蟹,這里給個簡單實(shí)例代碼乌妙,大家YY下
function setAttrs(dom, props) {
const ALL_KEYS = Object.keys(props);
ALL_KEYS.forEach(k =>{
const v = props[k];
// className
if(k === 'className'){
dom.setAttribute('class',v);
return;
}
if(k == "style") {
if(typeof v == "string") {
dom.style.cssText = v
}
if(typeof v == "object") {
for (let i in v) {
dom.style[i] = v[i]
}
}
return
}
if(k[0] == "o" && k[1] == "n") {
const capture = (k.indexOf("Capture") != -1)
dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
return
}
dom.setAttribute(k, v)
})
}
如上,其實(shí)我們已經(jīng)實(shí)現(xiàn)了DOM diff了建钥,但是存在一個問題.
如下圖藤韵,老集合中包含節(jié)點(diǎn):A、B熊经、C泽艘、D,更新后的新集合中包含節(jié)點(diǎn):B镐依、A匹涮、D、C馋吗,此時新老集合進(jìn)行 diff 差異化對比焕盟,發(fā)現(xiàn) B != A,則創(chuàng)建并插入 B 至新集合宏粤,刪除老集合 A脚翘;以此類推,創(chuàng)建并插入 A绍哎、D 和 C来农,刪除 B、C 和 D崇堰。
針對這一現(xiàn)象沃于,React 提出優(yōu)化策略:允許開發(fā)者對同一層級的同組子節(jié)點(diǎn),添加唯一 key 進(jìn)行區(qū)分海诲,雖然只是小小的改動繁莹,性能上卻發(fā)生了翻天覆地的變化!
具體介紹可以參照 https://zhuanlan.zhihu.com/p/20346379
這里我們放到代碼實(shí)現(xiàn)上:
/**
* Diff two list in O(N).
* @param {Array} oldList - Original List
* @param {Array} newList - List After certain insertions, removes, or moves
* @return {Object} - {moves: <Array>}
* - moves is a list of actions that telling how to remove and insert
*/
function diff (oldList, newList, key) {
var oldMap = makeKeyIndexAndFree(oldList, key)
var newMap = makeKeyIndexAndFree(newList, key)
var newFree = newMap.free
var oldKeyIndex = oldMap.keyIndex
var newKeyIndex = newMap.keyIndex
var moves = []
// a simulate list to manipulate
var children = []
var i = 0
var item
var itemKey
var freeIndex = 0
// first pass to check item in old list: if it's removed or not
// 遍歷舊的集合
while (i < oldList.length) {
item = oldList[i]
itemKey = getItemKey(item, key)//itemKey a
// 是否可以取到
if (itemKey) {
// 判斷新集合中是否有這個屬性特幔,如果沒有則push null
if (!newKeyIndex.hasOwnProperty(itemKey)) {
children.push(null)
} else {
// 如果有 去除在新列表中的位置
var newItemIndex = newKeyIndex[itemKey]
children.push(newList[newItemIndex])
}
} else {
var freeItem = newFree[freeIndex++]
children.push(freeItem || null)
}
i++
}
// children [{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
var simulateList = children.slice(0)//[{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
// remove items no longer exist
i = 0
while (i < simulateList.length) {
if (simulateList[i] === null) {
remove(i)
removeSimulate(i)
} else {
i++
}
}
// i is cursor pointing to a item in new list
// j is cursor pointing to a item in simulateList
var j = i = 0
while (i < newList.length) {
item = newList[i]
itemKey = getItemKey(item, key)//c
var simulateItem = simulateList[j] //{id:"a"}
var simulateItemKey = getItemKey(simulateItem, key)//a
if (simulateItem) {
if (itemKey === simulateItemKey) {
j++
} else {
// 新增項(xiàng)咨演,直接插入
if (!oldKeyIndex.hasOwnProperty(itemKey)) {
insert(i, item)
} else {
// if remove current simulateItem make item in right place
// then just remove it
var nextItemKey = getItemKey(simulateList[j + 1], key)
if (nextItemKey === itemKey) {
remove(i)
removeSimulate(j)
j++ // after removing, current j is right, just jump to next one
} else {
// else insert item
insert(i, item)
}
}
}
} else {
insert(i, item)
}
i++
}
//if j is not remove to the end, remove all the rest item
var k = simulateList.length - j
while (j++ < simulateList.length) {
k--
remove(k + i)
}
// 記錄舊的列表中移除項(xiàng) {index:3,type:0}
function remove (index) {
var move = {index: index, type: 0}
moves.push(move)
}
function insert (index, item) {
var move = {index: index, item: item, type: 1}
moves.push(move)
}
// 刪除simulateList中null
function removeSimulate (index) {
simulateList.splice(index, 1)
}
return {
moves: moves,
children: children
}
}
/**
* Convert list to key-item keyIndex object.
* 將列表轉(zhuǎn)換為 key-item 的鍵值對象
* [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}] -> [a:0,b:1,c:2...]
* @param {Array} list
* @param {String|Function} key
*/
function makeKeyIndexAndFree (list, key) {
var keyIndex = {}
var free = []
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i]
var itemKey = getItemKey(item, key)
if (itemKey) {
keyIndex[itemKey] = i
} else {
free.push(item)
}
}
return {
keyIndex: keyIndex,
free: free
}
}
// 獲取置頂key的value
function getItemKey (item, key) {
if (!item || !key) return void 666
return typeof key === 'string'
? item[key]
: key(item)
}
exports.makeKeyIndexAndFree = makeKeyIndexAndFree
exports.diffList = diff
代碼參照:list-diff 具體的注釋都已經(jīng)加上。
使用如下:
import {diffList as diff} from './lib/diffList';
var oldList = [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}]
var newList = [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
var moves = diff(oldList, newList, "id")
// type 0 表示移除, type 1 表示插入
// moves: [
// {index: 3, type: 0},
// {index: 0, type: 1, item: {id: "c"}},
// {index: 3, type: 0},
// {index: 4, type: 1, item: {id: "f"}}
// ]
console.log(moves)
moves.moves.forEach(function(move) {
if (move.type === 0) {
oldList.splice(move.index, 1) // type 0 is removing
} else {
oldList.splice(move.index, 0, move.item) // type 1 is inserting
}
})
// now `oldList` is equal to `newList`
// [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
console.log(oldList)
這里我最困惑的地方時蚯斯,實(shí)現(xiàn)diff都是index為索引薄风,深度優(yōu)先遍歷饵较,如果存在這種移動操作的話,那么之前我補(bǔ)丁patches里記錄的index不就沒有意義了么遭赂?循诉?
在 后來在開源的simple-virtual-dom中找到了index作為索引和標(biāo)識去實(shí)現(xiàn)diff的答案。
- 第一點(diǎn):在createElement的時候撇他,去記錄每一元素children的count數(shù)量
function Element(tagName, props, children) {
if (!(this instanceof Element)) {
if (!_.isArray(children) && children != null) {
children = _.slice(arguments, 2).filter(_.truthy)
}
return new Element(tagName, props, children)
}
if (_.isArray(props)) {
children = props
props = {}
}
this.tagName = tagName
this.props = props || {}
this.children = children || []
this.key = props ?
props.key :
void 666
var count = 0
_.each(this.children, function (child, i) {
if (child instanceof Element) {
count += child.count
} else {
children[i] = '' + child
}
count++
})
this.count = count
}
- 第二點(diǎn)茄猫,在diff算法中,遇到移動的時候困肩,我們需要及時更新我們?nèi)肿兞縤ndex募疮,核心代碼
(leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1
。完整代碼如下:
function diffChildren(oldChildren, newChildren, index, patches, currentPatch) {
var diffs = diffList(oldChildren, newChildren, 'key')
newChildren = diffs.children
if (diffs.moves.length) {
var reorderPatch = {
type: patch.REORDER,
moves: diffs.moves
}
currentPatch.push(reorderPatch)
}
var leftNode = null
var currentNodeIndex = index
_.each(oldChildren, function (child, i) {
var newChild = newChildren[i]
currentNodeIndex = (leftNode && leftNode.count) ?
currentNodeIndex + leftNode.count + 1 :
currentNodeIndex + 1
dfsWalk(child, newChild, currentNodeIndex, patches)
leftNode = child
})
}
話說僻弹,這里困擾了我好久好久。他嚷。蹋绽。。
回到開頭
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
也就說明了這段代碼的必要性筋蓖。
0.3中diff的實(shí)現(xiàn)
最后我們在看下0.3中diff的實(shí)現(xiàn):
updateMultiChild: function(nextChildren, transaction) {
if (!nextChildren && !this._renderedChildren) {
return;
} else if (nextChildren && !this._renderedChildren) {
this._renderedChildren = {}; // lazily allocate backing store with nothing
} else if (!nextChildren && this._renderedChildren) {
nextChildren = {};
}
var rootDomIdDot = this._rootNodeID + '.';
var markupBuffer = null; // Accumulate adjacent new children markup.
var numPendingInsert = 0; // How many root nodes are waiting in markupBuffer
var loopDomIndex = 0; // Index of loop through new children.
var curChildrenDOMIndex = 0; // See (Comment 1)
for (var name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {continue;}
// 獲取當(dāng)前節(jié)點(diǎn)與要渲染的節(jié)點(diǎn)
var curChild = this._renderedChildren[name];
var nextChild = nextChildren[name];
// 是否兩個節(jié)點(diǎn)都存在,且類型相同
if (shouldManageExisting(curChild, nextChild)) {
// 如果有插入標(biāo)示卸耘,之后又循環(huán)到了不需要插入的節(jié)點(diǎn),則直接插入粘咖,并把插入標(biāo)示制空
if (markupBuffer) {
this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
markupBuffer = null;
}
numPendingInsert = 0;
// 如果找到當(dāng)前要渲染的節(jié)點(diǎn)序號比最大序號小蚣抗,則移動節(jié)點(diǎn)
/*
* 在0.3中,沒有根據(jù)key做diff瓮下,而是通過Object中的key作為索引
* 比如{a,b,c}替換成{c,b,c}
* b._domIndex = 1挪到loopDomIndex = 1的位置翰铡,就是原地不動
a._domIndex = 0挪到loopDomIndex = 2的位置,也就是和c換位
*/
if (curChild._domIndex < curChildrenDOMIndex) { // (Comment 2)
this.enqueueMove(curChild._domIndex, loopDomIndex);
}
curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
// 遞歸更新子節(jié)點(diǎn)Props,調(diào)用子節(jié)點(diǎn)dom-diff...
!nextChild.props.isStatic &&
curChild.receiveProps(nextChild.props, transaction);
curChild._domIndex = loopDomIndex;
} else {
// 當(dāng)前存在讽坏,執(zhí)行刪除
if (curChild) { // !shouldUpdate && curChild => delete
this.enqueueUnmountChildByName(name, curChild);
curChildrenDOMIndex =
Math.max(curChild._domIndex, curChildrenDOMIndex);
}
// 當(dāng)前不存在锭魔,下個節(jié)點(diǎn)存在, 執(zhí)行插入,渲染下個節(jié)點(diǎn)
if (nextChild) { // !shouldUpdate && nextChild => insert
this._renderedChildren[name] = nextChild;
// 渲染下個節(jié)點(diǎn)
var nextMarkup =
nextChild.mountComponent(rootDomIdDot + name, transaction);
markupBuffer = markupBuffer ? markupBuffer + nextMarkup : nextMarkup;
numPendingInsert++;
nextChild._domIndex = loopDomIndex;
}
}
loopDomIndex = nextChild ? loopDomIndex + 1 : loopDomIndex;
}
// 執(zhí)行插入操作路呜,插入位置計算方式如下:
// 要渲染的節(jié)點(diǎn)位置-要插入的節(jié)點(diǎn)個數(shù):比如當(dāng)前要渲染的節(jié)點(diǎn)index=3迷捧,當(dāng)前節(jié)點(diǎn)只有一個,也就是index=1胀葱。
// 如<div>1</div>渲染成<div>1</div><div>2</div><div>3</div>
// 那么從<div>2</div>開始就開始加入buffer,最終buffer內(nèi)容為<div>2</div><div>3</div>
// 那么要插入的位置為 3 - 1 = 2。我們以<div>1</div>為1咨跌,就是把buffer插入2的位置丐重,也就是<div>1</div>后面
if (markupBuffer) {
this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
}
// 循環(huán)老節(jié)點(diǎn)
for (var childName in this._renderedChildren) {
if (!this._renderedChildren.hasOwnProperty(childName)) { continue; }
var child = this._renderedChildren[childName];
// 當(dāng)前節(jié)點(diǎn)存在,下個節(jié)點(diǎn)不存在晌该,刪除
if (child && !nextChildren[childName]) {
this.enqueueUnmountChildByName(childName, child);
}
}
// 一次提交所有操作
this.processChildDOMOperationsQueue();
}