摘要:前言在上一章我們學(xué)習(xí)了,等模塊,在這一篇我們將會(huì)學(xué)習(xí)到的核心功能和功能。如果父節(jié)點(diǎn)沒(méi)變化,我們就比較所有同層的子節(jié)點(diǎn),對(duì)這些子節(jié)點(diǎn)進(jìn)行刪除創(chuàng)建移位操作。只需要對(duì)兩個(gè)進(jìn)行判斷是否相似,如果相似,則對(duì)他們進(jìn)行操作,否則直接用替換。
前言
在上一章我們學(xué)習(xí)了,modules,vnode,h,htmldomapi,is等模塊,在這一篇我們將會(huì)學(xué)習(xí)到
snabbdom的核心功能——patchVnode和updateChildren功能。
首先我們先從簡(jiǎn)單的部分開(kāi)始,比如一些工具函數(shù),我將逐個(gè)來(lái)講解他們的用處
sameNode這個(gè)函數(shù)主要用于比較oldvnode與vnode同層次節(jié)點(diǎn)的比較,如果同層次節(jié)點(diǎn)的key和sel都相同
我們就可以保留這個(gè)節(jié)點(diǎn),否則直接替換節(jié)點(diǎn)
function sameVnode(vnode1, vnode2) { return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; }createKeyToOldIdx
這個(gè)函數(shù)的功能十分簡(jiǎn)單,就是將oldvnode數(shù)組中位置對(duì)oldvnode.key的映射轉(zhuǎn)換為oldvnode.key
對(duì)位置的映射
function createKeyToOldIdx(children, beginIdx, endIdx) { var i, map = {}, key; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) map[key] = i; } return map; }hook
snabbdom在全局下有6種類(lèi)型的鉤子,觸發(fā)這些鉤子時(shí),會(huì)調(diào)用對(duì)應(yīng)的函數(shù)對(duì)節(jié)點(diǎn)的狀態(tài)進(jìn)行更改
首先我們來(lái)看看有哪些鉤子:
Name | Triggered when | Arguments to callback |
---|---|---|
pre | the patch process begins (patch開(kāi)始時(shí)觸發(fā)) | none |
init | a vnode has been added (vnode被創(chuàng)建時(shí)觸發(fā)) | vnode |
create | a DOM element has been created based on a vnode (vnode轉(zhuǎn)換為真實(shí)DOM節(jié)點(diǎn)時(shí)觸發(fā) | emptyVnode, vnode |
insert | an element has been inserted into the DOM (插入到DOM樹(shù)時(shí)觸發(fā)) | vnode |
prepatch | an element is about to be patched (元素準(zhǔn)備patch前觸發(fā)) | oldVnode, vnode |
update | an element is being updated (元素更新時(shí)觸發(fā)) | oldVnode, vnode |
postpatch | an element has been patched (元素patch完觸發(fā)) | oldVnode, vnode |
destroy | an element is directly or indirectly being removed (元素被刪除時(shí)觸發(fā)) | vnode |
remove | an element is directly being removed from the DOM (元素從父節(jié)點(diǎn)刪除時(shí)觸發(fā),和destory略有不同,remove只影響到被移除節(jié)點(diǎn)中最頂層的節(jié)點(diǎn)) | vnode, removeCallback |
post | the patch process is done (patch完成后觸發(fā)) | none |
然后,下面列出鉤子對(duì)應(yīng)的狀態(tài)更新函數(shù):
create => style,class,dataset,eventlistener,props,hero
update => style,class,dataset,eventlistener,props,hero
remove => style
destory => eventlistener,style,hero
pre => hero
post => hero
好了,簡(jiǎn)單的都看完了,接下來(lái)我們開(kāi)始打大boss了,第一關(guān)就是init函數(shù)了
initinit函數(shù)有兩個(gè)參數(shù)modules和api,其中modules是init依賴(lài)的模塊,如attribute、props
、eventlistener這些模塊,api則是對(duì)封裝真實(shí)DOM操作的工具函數(shù)庫(kù),如果我們沒(méi)有傳入,則默認(rèn)
使用snabbdom提供的htmldomapi。init還包含了許多vnode和真實(shí)DOM之間的操作和注冊(cè)全局鉤子,
還有patchVnode和updateChildren這兩個(gè)重要功能,然后返回一個(gè)patch函數(shù)
//注冊(cè)鉤子的回調(diào),在發(fā)生狀態(tài)變更時(shí),觸發(fā)對(duì)應(yīng)屬性變更 for (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = []; for (j = 0; j < modules.length; ++j) { if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]]); } }emptyNodeAt
這個(gè)函數(shù)主要的功能是將一個(gè)真實(shí)DOM節(jié)點(diǎn)轉(zhuǎn)化成vnode形式, 我們知道當(dāng)我們需要remove一個(gè)vnode時(shí),會(huì)觸發(fā)remove鉤子作攔截器,只有在所有remove鉤子 這個(gè)函數(shù)用于手動(dòng)觸發(fā)destory鉤子回調(diào),主要步驟如下: 先調(diào)用vnode上的destory 再調(diào)用全局下的destory
遞歸調(diào)用子vnode的destory 這個(gè)函數(shù)主要功能是批量刪除DOM節(jié)點(diǎn),需要配合invokeDestoryHook和createRmCb服用,效果更佳 調(diào)用invokeDestoryHook以觸發(fā)destory回調(diào) 調(diào)用createRmCb來(lái)開(kāi)始對(duì)remove回調(diào)進(jìn)行計(jì)數(shù)
刪除DOM節(jié)點(diǎn) 就如太極有陰就有陽(yáng)一樣,既然我們有remove操作,肯定也有createelm的操作,這個(gè)函數(shù)主要功能 初始化vnode,調(diào)用init鉤子 創(chuàng)建對(duì)應(yīng)tagname的DOM element節(jié)點(diǎn),并將vnode.sel中的id名和class名掛載上去
如果有子vnode,遞歸創(chuàng)建DOM element節(jié)點(diǎn),并添加到父vnode對(duì)應(yīng)的element節(jié)點(diǎn)上去, vnode轉(zhuǎn)換成dom節(jié)點(diǎn)操作完成后,調(diào)用create鉤子
如果vnode上有insert鉤子,那么就將這個(gè)vnode放入insertedVnodeQueue中作記錄,到時(shí) 這個(gè)函數(shù)十分簡(jiǎn)單,就是將vnode轉(zhuǎn)換后的dom節(jié)點(diǎn)插入到dom樹(shù)的指定位置中去 說(shuō)完上面的節(jié)點(diǎn)工具函數(shù)之后,我們就開(kāi)始看如何進(jìn)行patch操作了,首先我們從patch,也就是init 首先我們需要明確的一個(gè)是,如果按照傳統(tǒng)的diff算法,那么為了找到最小變化,需要逐層逐層的去 真正對(duì)vnode內(nèi)部patch的還是得靠patchVnode。讓我們看看他到底做了什么? 對(duì)于同層的子節(jié)點(diǎn),snabbdom主要有刪除、創(chuàng)建的操作,同時(shí)通過(guò)移位的方法,達(dá)到最大復(fù)用存在 oldStartIdx => 舊頭索引 oldEndIdx => 舊尾索引 newStartIdx => 新頭索引 newEndIdx => 新尾索引 然后開(kāi)始將舊子節(jié)點(diǎn)組和新子節(jié)點(diǎn)組進(jìn)行逐一比對(duì),直到遍歷完任一子節(jié)點(diǎn)組,比對(duì)策略有5種: oldStartVnode和newStartVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,然后新舊頭索引都后移 oldEndVnode和newEndVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,然后新舊尾索引前移
oldStartVnode和newEndVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,將舊節(jié)點(diǎn)移位到最后 oldEndVnode和newStartVnode進(jìn)行比對(duì),處理和上面類(lèi)似,只不過(guò)改為左移
如果以上情況都失敗了,我們就只能復(fù)用key相同的節(jié)點(diǎn)了。首先我們要通過(guò)createKeyToOldIdx 遍歷完之后,將剩余的新Vnode添加到最后一個(gè)新節(jié)點(diǎn)的位置后或者刪除多余的舊節(jié)點(diǎn) 至此,snabbdom的主要功能就分析完了 文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88233.html
如將轉(zhuǎn)換為{sel:"div#a.b.c",data:{},children:[],text:undefined,elm: function emptyNodeAt(elm) {
var id = elm.id ? "#" + elm.id : "";
var c = elm.className ? "." + elm.className.split(" ").join(".") : "";
return VNode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
}
createRmCb
回調(diào)函數(shù)都觸發(fā)完才會(huì)將節(jié)點(diǎn)從父節(jié)點(diǎn)刪除,而這個(gè)函數(shù)提供的就是對(duì)remove鉤子回調(diào)操作的計(jì)數(shù)功能function createRmCb(childElm, listeners) {
return function() {
if (--listeners === 0) {
var parent = api.parentNode(childElm);
api.removeChild(parent, childElm);
}
};
}
invokeDestoryHook
function invokeDestroyHook(vnode) {
var i, j, data = vnode.data;
if (isDef(data)) {
//先觸發(fā)該節(jié)點(diǎn)上的destory回調(diào)
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
//在觸發(fā)全局下的destory回調(diào)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
//遞歸觸發(fā)子節(jié)點(diǎn)的destory回調(diào)
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j]);
}
}
}
}
removeVnodes
主要步驟如下: /**
*
* @param parentElm 父節(jié)點(diǎn)
* @param vnodes 刪除節(jié)點(diǎn)數(shù)組
* @param startIdx 刪除起始坐標(biāo)
* @param endIdx 刪除結(jié)束坐標(biāo)
*/
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var i, listeners, rm, ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.sel)) {
//調(diào)用destroy鉤子
invokeDestroyHook(ch);
//對(duì)全局remove鉤子進(jìn)行計(jì)數(shù)
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm, listeners);
//調(diào)用全局remove回調(diào)函數(shù),并每次減少一個(gè)remove鉤子計(jì)數(shù)
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
//調(diào)用內(nèi)部vnode.data.hook中的remove鉤子(只有一個(gè))
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
i(ch, rm);
} else {
//如果沒(méi)有內(nèi)部remove鉤子,需要調(diào)用rm,確保能夠remove節(jié)點(diǎn)
rm();
}
} else { // Text node
api.removeChild(parentElm, ch.elm);
}
}
}
}
createElm
如下:否則如果有text屬性,則創(chuàng)建text節(jié)點(diǎn),并添加到父vnode對(duì)應(yīng)的element節(jié)點(diǎn)上去
再在全局批量調(diào)用insert鉤子回調(diào)
function createElm(vnode, insertedVnodeQueue) {
var i, data = vnode.data;
if (isDef(data)) {
//當(dāng)節(jié)點(diǎn)上存在hook而且hook中有init鉤子時(shí),先調(diào)用init回調(diào),對(duì)剛創(chuàng)建的vnode進(jìn)行處理
if (isDef(i = data.hook) && isDef(i = i.init)) {
i(vnode);
//獲取init鉤子修改后的數(shù)據(jù)
data = vnode.data;
}
}
var elm, children = vnode.children, sel = vnode.sel;
if (isDef(sel)) {
// Parse selector
var hashIdx = sel.indexOf("#");
//先id后class
var dotIdx = sel.indexOf(".", hashIdx);
var hash = hashIdx > 0 ? hashIdx : sel.length;
var dot = dotIdx > 0 ? dotIdx : sel.length;
var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
//創(chuàng)建一個(gè)DOM節(jié)點(diǎn)引用,并對(duì)其屬性實(shí)例化
elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag): api.createElement(tag);
//獲取id名 #a --> a
if (hash < dot) elm.id = sel.slice(hash + 1, dot);
//獲取類(lèi)名,并格式化 .a.b --> a b
if (dotIdx > 0) elm.className = sel.slice(dot + 1).replace(/./g, " ");
//如果存在子元素Vnode節(jié)點(diǎn),則遞歸將子元素節(jié)點(diǎn)插入到當(dāng)前Vnode節(jié)點(diǎn)中,并將已插入的子元素節(jié)點(diǎn)在insertedVnodeQueue中作記錄
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
api.appendChild(elm, createElm(children[i], insertedVnodeQueue));
}
//如果存在子文本節(jié)點(diǎn),則直接將其插入到當(dāng)前Vnode節(jié)點(diǎn)
} else if (is.primitive(vnode.text)) {
api.appendChild(elm, api.createTextNode(vnode.text));
}
//當(dāng)創(chuàng)建完畢后,觸發(fā)全局create鉤子回調(diào)
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (i.create) i.create(emptyNode, vnode);
//如果有insert鉤子,則推進(jìn)insertedVnodeQueue中作記錄,從而實(shí)現(xiàn)批量插入觸發(fā)insert回調(diào)
if (i.insert) insertedVnodeQueue.push(vnode);
}
}
//如果沒(méi)聲明選擇器,則說(shuō)明這個(gè)是一個(gè)text節(jié)點(diǎn)
else {
elm = vnode.elm = api.createTextNode(vnode.text);
}
return vnode.elm;
}
addVnodes
function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
api.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before);
}
}
返回的函數(shù)開(kāi)始
搜索比較,這樣時(shí)間復(fù)雜度將會(huì)達(dá)到 O(n^3)的級(jí)別,代價(jià)十分高,考慮到節(jié)點(diǎn)變化很少是跨層次的,
vdom采取的是一種簡(jiǎn)化的思路,只比較同層節(jié)點(diǎn),如果不同,那么即使該節(jié)點(diǎn)的子節(jié)點(diǎn)沒(méi)變化,我們
也不復(fù)用,直接將從父節(jié)點(diǎn)開(kāi)始的子樹(shù)全部刪除,然后再重新創(chuàng)建節(jié)點(diǎn)添加到新的位置。如果父節(jié)點(diǎn)
沒(méi)變化,我們就比較所有同層的子節(jié)點(diǎn),對(duì)這些子節(jié)點(diǎn)進(jìn)行刪除、創(chuàng)建、移位操作。有了這個(gè)思想,
理解patch也十分簡(jiǎn)單了。patch只需要對(duì)兩個(gè)vnode進(jìn)行判斷是否相似,如果相似,則對(duì)他們進(jìn)行
patchVnode操作,否則直接用vnode替換oldvnode。return function(oldVnode, vnode) {
var i, elm, parent;
//記錄被插入的vnode隊(duì)列,用于批觸發(fā)insert
var insertedVnodeQueue = [];
//調(diào)用全局pre鉤子
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
//如果oldvnode是dom節(jié)點(diǎn),轉(zhuǎn)化為oldvnode
if (isUndef(oldVnode.sel)) {
oldVnode = emptyNodeAt(oldVnode);
}
//如果oldvnode與vnode相似,進(jìn)行更新
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue);
} else {
//否則,將vnode插入,并將oldvnode從其父節(jié)點(diǎn)上直接刪除
elm = oldVnode.elm;
parent = api.parentNode(elm);
createElm(vnode, insertedVnodeQueue);
if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
removeVnodes(parent, [oldVnode], 0, 0);
}
}
//插入完后,調(diào)用被插入的vnode的insert鉤子
for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
}
//然后調(diào)用全局下的post鉤子
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
//返回vnode用作下次patch的oldvnode
return vnode;
};
patchVnode
function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
var i, hook;
//在patch之前,先調(diào)用vnode.data的prepatch鉤子
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
i(oldVnode, vnode);
}
var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children;
//如果oldvnode和vnode的引用相同,說(shuō)明沒(méi)發(fā)生任何變化直接返回,避免性能浪費(fèi)
if (oldVnode === vnode) return;
//如果oldvnode和vnode不同,說(shuō)明vnode有更新
//如果vnode和oldvnode不相似則直接用vnode引用的DOM節(jié)點(diǎn)去替代oldvnode引用的舊節(jié)點(diǎn)
if (!sameVnode(oldVnode, vnode)) {
var parentElm = api.parentNode(oldVnode.elm);
elm = createElm(vnode, insertedVnodeQueue);
api.insertBefore(parentElm, elm, oldVnode.elm);
removeVnodes(parentElm, [oldVnode], 0, 0);
return;
}
//如果vnode和oldvnode相似,那么我們要對(duì)oldvnode本身進(jìn)行更新
if (isDef(vnode.data)) {
//首先調(diào)用全局的update鉤子,對(duì)vnode.elm本身屬性進(jìn)行更新
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
//然后調(diào)用vnode.data里面的update鉤子,再次對(duì)vnode.elm更新
i = vnode.data.hook;
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
}
//如果vnode不是text節(jié)點(diǎn)
if (isUndef(vnode.text)) {
//如果vnode和oldVnode都有子節(jié)點(diǎn)
if (isDef(oldCh) && isDef(ch)) {
//當(dāng)Vnode和oldvnode的子節(jié)點(diǎn)不同時(shí),調(diào)用updatechilren函數(shù),diff子節(jié)點(diǎn)
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
}
//如果vnode有子節(jié)點(diǎn),oldvnode沒(méi)子節(jié)點(diǎn)
else if (isDef(ch)) {
//oldvnode是text節(jié)點(diǎn),則將elm的text清除
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
//并添加vnode的children
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
}
//如果oldvnode有children,而vnode沒(méi)children,則移除elm的children
else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
//如果vnode和oldvnode都沒(méi)chidlren,且vnode沒(méi)text,則刪除oldvnode的text
else if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
}
//如果oldvnode的text和vnode的text不同,則更新為vnode的text
else if (oldVnode.text !== vnode.text) {
api.setTextContent(elm, vnode.text);
}
//patch完,觸發(fā)postpatch鉤子
if (isDef(hook) && isDef(i = hook.postpatch)) {
i(oldVnode, vnode);
}
}
updateChildren
節(jié)點(diǎn)的目的,其中需要維護(hù)四個(gè)索引,分別是:然后舊頭索引后移,尾索引前移,為什么要這樣做呢?我們思考一種情況,如舊節(jié)點(diǎn)為【5,1,2,3,4】
,新節(jié)點(diǎn)為【1,2,3,4,5】,如果缺乏這種判斷,意味著需要先將5->1,1->2,2->3,3->4,4->5五
次刪除插入操作,即使是有了key-index來(lái)復(fù)用,也會(huì)出現(xiàn)也會(huì)出現(xiàn)【5,1,2,3,4】->
【1,5,2,3,4】->【1,2,5,3,4】->【1,2,3,5,4】->【1,2,3,4,5】共4次操作,如果
有了這種判斷,我們只需要將5插入到舊尾索引后面即可,從而實(shí)現(xiàn)右移
創(chuàng)建key-index的映射,如果新節(jié)點(diǎn)在舊節(jié)點(diǎn)中不存在,我們將它插入到舊頭索引節(jié)點(diǎn)前,
然后新頭索引向后;如果新節(jié)點(diǎn)在就舊節(jié)點(diǎn)組中存在,先找到對(duì)應(yīng)的舊節(jié)點(diǎn),然后patch,并將
舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)設(shè)置為undefined,代表已經(jīng)遍歷過(guò)了,不再遍歷,否則可能存在重復(fù)
插入的問(wèn)題,最后將節(jié)點(diǎn)移位到舊頭索引節(jié)點(diǎn)之前,新頭索引向后
/**
*
* @param parentElm 父節(jié)點(diǎn)
* @param oldCh 舊節(jié)點(diǎn)數(shù)組
* @param newCh 新節(jié)點(diǎn)數(shù)組
* @param insertedVnodeQueue
*/
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
var oldStartIdx = 0, newStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx, idxInOld, elmToMove, before;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
}
//如果舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)相同,
else if (sameVnode(oldStartVnode, newStartVnode)) {
//對(duì)舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)進(jìn)行diff更新, 從而達(dá)到復(fù)用節(jié)點(diǎn)效果
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
//舊頭索引向后
oldStartVnode = oldCh[++oldStartIdx];
//新頭索引向后
newStartVnode = newCh[++newStartIdx];
}
//如果舊尾索引節(jié)點(diǎn)和新尾索引節(jié)點(diǎn)相似,可以復(fù)用
else if (sameVnode(oldEndVnode, newEndVnode)) {
//舊尾索引節(jié)點(diǎn)和新尾索引節(jié)點(diǎn)進(jìn)行更新
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
//舊尾索引向前
oldEndVnode = oldCh[--oldEndIdx];
//新尾索引向前
newEndVnode = newCh[--newEndIdx];
}
//如果舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)相似,可以通過(guò)移動(dòng)來(lái)復(fù)用
//如舊節(jié)點(diǎn)為【5,1,2,3,4】,新節(jié)點(diǎn)為【1,2,3,4,5】,如果缺乏這種判斷,意味著
//那樣需要先將5->1,1->2,2->3,3->4,4->5五次刪除插入操作,即使是有了key-index來(lái)復(fù)用,
// 也會(huì)出現(xiàn)【5,1,2,3,4】->【1,5,2,3,4】->【1,2,5,3,4】->【1,2,3,5,4】->【1,2,3,4,5】
// 共4次操作,如果有了這種判斷,我們只需要將5插入到最后一次操作即可
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
//原理與上面相同
else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
//如果上面的判斷都不通過(guò),我們就需要key-index表來(lái)達(dá)到最大程度復(fù)用了
else {
//如果不存在舊節(jié)點(diǎn)的key-index表,則創(chuàng)建
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
//找到新節(jié)點(diǎn)在舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)的位置
idxInOld = oldKeyToIdx[newStartVnode.key];
//如果新節(jié)點(diǎn)在舊節(jié)點(diǎn)中不存在,我們將它插入到舊頭索引節(jié)點(diǎn)前,然后新頭索引向后
if (isUndef(idxInOld)) { // New element
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
newStartVnode = newCh[++newStartIdx];
} else {
//如果新節(jié)點(diǎn)在就舊節(jié)點(diǎn)組中存在,先找到對(duì)應(yīng)的舊節(jié)點(diǎn)
elmToMove = oldCh[idxInOld];
//先將新節(jié)點(diǎn)和對(duì)應(yīng)舊節(jié)點(diǎn)作更新
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
//然后將舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)設(shè)置為undefined,代表已經(jīng)遍歷過(guò)了,不在遍歷,否則可能存在重復(fù)插入的問(wèn)題
oldCh[idxInOld] = undefined;
//插入到舊頭索引節(jié)點(diǎn)之前
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
//新頭索引向后
newStartVnode = newCh[++newStartIdx];
}
}
}
//當(dāng)舊頭索引大于舊尾索引時(shí),代表舊節(jié)點(diǎn)組已經(jīng)遍歷完,將剩余的新Vnode添加到最后一個(gè)新節(jié)點(diǎn)的位置后
if (oldStartIdx > oldEndIdx) {
before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx+1].elm;
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
}
//如果新節(jié)點(diǎn)組先遍歷完,那么代表舊節(jié)點(diǎn)組中剩余節(jié)點(diǎn)都不需要,所以直接刪除
else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
摘要:閑聊在學(xué)的過(guò)程中,虛擬應(yīng)該是聽(tīng)的最多的概念之一,得知其是借鑒進(jìn)行開(kāi)發(fā),故習(xí)之。以我的觀點(diǎn)來(lái)看,多個(gè)相同元素渲染時(shí),則需要為每個(gè)元素添加值。 閑聊:在學(xué)vue的過(guò)程中,虛擬dom應(yīng)該是聽(tīng)的最多的概念之一,得知其是借鑒snabbdom.js進(jìn)行開(kāi)發(fā),故習(xí)之。由于我工作處于IE8的環(huán)境,對(duì)ES6,TS這些知識(shí)的練習(xí)也只是淺嘗輒止,而snabbdom.js從v.0.5.4這個(gè)版本后開(kāi)始使用TS...
摘要:前言最近在學(xué)習(xí)的源碼,剛開(kāi)始看其源碼,著實(shí)找不到方向,因?yàn)槠湓诘膶?shí)現(xiàn)上還加入了很多本身的鉤子,加大了閱讀難度。 前言 最近在學(xué)習(xí)vue2.0的源碼,剛開(kāi)始看其vdom源碼,著實(shí)找不到方向,因?yàn)槠湓趘dom的實(shí)現(xiàn)上還加入了很多vue2.0本身的鉤子,加大了閱讀難度。于是看到第一行尤大說(shuō)vue2.0的vdom是在snabbdom的基礎(chǔ)上改過(guò)來(lái)的,而snabbdom只有不到300sloc,那...
摘要:司徒正美的一款了不起的化方案,支持到。行代碼內(nèi)實(shí)現(xiàn)一個(gè)胡子大哈實(shí)現(xiàn)的作品其實(shí)就是的了源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對(duì)比。 前言 在過(guò)去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開(kāi)始寫(xiě)了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱欤?..
摘要:那個(gè)率先改變的實(shí)例的返回值,就會(huì)傳遞給的回調(diào)函數(shù)。函數(shù)對(duì)函數(shù)的改進(jìn),體現(xiàn)在以下四點(diǎn)內(nèi)置執(zhí)行器。進(jìn)一步說(shuō),函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè)對(duì)象,而命令就是內(nèi)部命令的語(yǔ)法糖。中的本質(zhì)就是沒(méi)有的隱藏的組件。 1、原型 - jquery使用showImg(https://segmentfault.com/img/bVbwNcY?w=692&h=442);注釋 : 實(shí)例雖然不同,但是構(gòu)...
閱讀 544·2023-04-26 01:39
閱讀 4524·2021-11-16 11:45
閱讀 2624·2021-09-27 13:37
閱讀 900·2021-09-01 10:50
閱讀 3610·2021-08-16 10:50
閱讀 2232·2019-08-30 15:55
閱讀 2995·2019-08-30 15:55
閱讀 2266·2019-08-30 14:07