摘要:如果新舊的和都相同,說(shuō)明兩個(gè)相似,我們就可以保留舊的節(jié)點(diǎn),再具體去比較其差異性,在舊的上進(jìn)行打補(bǔ)丁否則直接替換節(jié)點(diǎn)。
總共寫(xiě)了四篇文章(都是自己的一些拙見(jiàn),僅供參考,請(qǐng)多多指教,我這邊也會(huì)持續(xù)修正加更新)
介紹一下snabbdom基本用法
介紹一下snabbdom渲染原理
介紹一下snabddom的diff算法和對(duì)key值的認(rèn)識(shí)
介紹一下對(duì)于兼容IE8的修改
這篇我將以自己的思路去解讀一下源碼(這里的源碼我為了兼容IE8有作修改);
對(duì)虛擬dom的理解通過(guò)js對(duì)象模擬出一個(gè)我們需要渲染到頁(yè)面上的dom樹(shù)的結(jié)構(gòu),實(shí)現(xiàn)了一個(gè)修改js對(duì)象即可修改頁(yè)面dom的快捷途徑,避免了我們‘手動(dòng)’再去一次次操作dom-api的繁瑣,而且其提供了算法可以使得用最少的dom操作進(jìn)行修改。從例子出發(fā),尋找切入點(diǎn)
var snabbdom = SnabbdomModule; var patch = snabbdom.init([ //導(dǎo)入相應(yīng)的模塊 DatasetModule, ClassModule, AttributesModule, PropsModule, StyleModule, EventlistenerModule ]); var h = HModule.h; var app = document.getElementById("app"); var newVnode = h("div#divId.red", {}, [h("p", {},"已改變")]) var vnode = h("div#divId.red", {}, [h("p",{},"2S后改變")]) vnode = patch(app, vnode); setTimeout(function() { vnode=patch(vnode, newVnode); }, 2000)
從上面的例子不難看出,我們需要從三個(gè)重點(diǎn)函數(shù) init patch h 切入,這三個(gè)函數(shù)分別的作用是:初始化模塊,對(duì)比渲染,構(gòu)建vnode;
而文章開(kāi)頭我說(shuō)了實(shí)現(xiàn)虛擬dom的第一步就是 通過(guò)js對(duì)象模擬出一個(gè)我們需要渲染到頁(yè)面上的dom樹(shù)的結(jié)構(gòu),所以"首當(dāng)其沖"就是需要先了解h函數(shù),如何將js對(duì)象封裝成vnode,vnode是我們定義的虛擬節(jié)點(diǎn),然后就是利用patch函數(shù)進(jìn)行渲染
構(gòu)建vnode h.jsvar HModule = {}; (function(HModule) { var VNode = VNodeModule.VNode; var is = isModule; /** * * @param sel 選擇器 * @param b 數(shù)據(jù) * @param childNode 子節(jié)點(diǎn) * @returns {{sel, data, children, text, elm, key}} */ //調(diào)用vnode函數(shù)將數(shù)據(jù)封裝成虛擬dom的數(shù)據(jù)結(jié)構(gòu)并返回,在調(diào)用之前會(huì)對(duì)數(shù)據(jù)進(jìn)行一個(gè)處理:是否含有數(shù)據(jù),是否含有子節(jié)點(diǎn),子節(jié)點(diǎn)類(lèi)型的判斷等 HModule.h = function(sel, b, childNode) { var data = {}, children, text, i; if (childNode !== undefined) { //如果childNode存在,則其為子節(jié)點(diǎn) //則h的第二項(xiàng)b就是data data = b; if (is.array(childNode)) { //如果子節(jié)點(diǎn)是數(shù)組,則存在子element節(jié)點(diǎn) children = childNode; } else if (is.primitive(childNode)) { //否則子節(jié)點(diǎn)為text節(jié)點(diǎn) text = childNode; } } else if (b !== undefined) { //如果只有b存在,childNode不存在,則b有可能是子節(jié)點(diǎn)也有可能是數(shù)據(jù) //數(shù)組代表子element節(jié)點(diǎn) if (is.array(b)) { children = b; } else if (is.primitive(b)) { //代表子文本節(jié)點(diǎn) text = b; } else { //代表數(shù)據(jù) data = b; } } if (is.array(children)) { for (i = 0; i < children.length; ++i) { //如果子節(jié)點(diǎn)數(shù)組中,存在節(jié)點(diǎn)是原始類(lèi)型,說(shuō)明該節(jié)點(diǎn)是text節(jié)點(diǎn),因此我們將它渲染為一個(gè)只包含text的VNode if (is.primitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]); } } //返回VNode return VNode(sel, data, children, text, undefined); } })(HModule)
h函數(shù)的主要工作就是把傳入的參數(shù)封裝為vnode
接下來(lái)看一下,vnode的結(jié)構(gòu)
vnode.jsvar VNodeModule = {}; (function(VNodeModule) { VNodeModule.VNode = function(sel, data, children, text, elm) { var key = data === undefined ? undefined : data.key; return { sel: sel, data: data, children: children, text: text, elm: elm, key: key }; } })(VNodeModule)
sel 對(duì)應(yīng)的是選擇器,如"div","div#a","div#a.b.c"的形式 data 對(duì)應(yīng)的是vnode綁定的數(shù)據(jù),可以有以下類(lèi)型:attribute、props、eventlistner、 class、dataset、hook children 子元素?cái)?shù)組 text 文本,代表該節(jié)點(diǎn)中的文本內(nèi)容 elm 里面存儲(chǔ)著對(duì)應(yīng)的真實(shí)dom element的引用 key vnode標(biāo)識(shí)符,主要是用在需要循環(huán)渲染的dom元素在進(jìn)行diff運(yùn)算時(shí)的優(yōu)化算法,例如ul>li,tobody>tr>td等
text和children是不會(huì)同時(shí)存在的,存在text代表子節(jié)點(diǎn)僅為文本節(jié)點(diǎn)
如:h("p",123) --->123
;存在children代表其子節(jié)點(diǎn)存在其他元素節(jié)點(diǎn)(也可以包含文本節(jié)點(diǎn)),需要將這些節(jié)點(diǎn)放入數(shù)組中 如:h("p",[h("h1",123),"222"]) --->
123
222
打印一下例子中調(diào)用h函數(shù)后的結(jié)構(gòu):
vnode:
newVnode:
關(guān)于elm這個(gè)值后面再說(shuō)
初始化模塊和對(duì)比渲染利用vnode生成我們的虛擬dom樹(shù)后,就需要開(kāi)始進(jìn)行渲染了;只所以說(shuō)是對(duì)比渲染,是因?yàn)樗秩镜臋C(jī)制不是直接把我們的設(shè)置好的vnode全部渲染,而是會(huì)進(jìn)行一次新舊vnode的對(duì)比,進(jìn)行差異渲染;
snabbdom.jsinit函數(shù)
function init(modules, api) { ... }
它有兩個(gè)參數(shù),第一個(gè)是需要加載的模塊數(shù)組,第二個(gè)是操作dom的api,一般我們只需要傳入第一個(gè)參數(shù)即可
1.模塊的初始化
先拿個(gè)模塊舉例:
var ClassModule = {}; function updateClass(oldVnode, vnode){} ClassModule.create = updateClass; ClassModule.update = updateClass;
var hooks = ["create", "update", "remove", "destroy", "pre", "post"]; //全局鉤子:modules自帶的鉤子函數(shù) function init(modules, api) { var i, j, cbs = {}; ... 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]]); } } ... }
上面就是模塊初始化的核心,事先在模塊中定義好鉤子函數(shù)(即模塊對(duì)于vnode的操作),然后在init函數(shù)中依次將這些模塊的鉤子函數(shù)加載進(jìn)來(lái),放在一個(gè)對(duì)象中保存,等待調(diào)用;
ps:init函數(shù)里面還會(huì)定義一些功能函數(shù),等用到的時(shí)候再說(shuō),然后下一個(gè)需要分析的就是init被調(diào)用后會(huì)return一個(gè)函數(shù)---patch函數(shù)(這個(gè)函數(shù)是自己定義的一個(gè)變量名);
2.調(diào)用patch函數(shù)進(jìn)行對(duì)比渲染
????在沒(méi)看源碼之前,我一直以為snabbdom的對(duì)比渲染是會(huì)把新舊vnode對(duì)比結(jié)果產(chǎn)生一個(gè)差異對(duì)象,然后在利用這個(gè)差異對(duì)象再進(jìn)行渲染,后面看了后發(fā)現(xiàn)snabbdom這邊是在對(duì)比的同時(shí)就直接利用dom的API在舊的dom上進(jìn)行修改,而這些操作(渲染)就是定義在我們前面加載的模塊中。
這里需要說(shuō)一下snabbdom的對(duì)比策略是針對(duì)同層級(jí)的節(jié)點(diǎn)進(jìn)行對(duì)比
其實(shí)這里就有一個(gè)小知識(shí)點(diǎn),bfs---廣度優(yōu)先遍歷
廣度優(yōu)先遍歷從某個(gè)頂點(diǎn)出發(fā),首先訪(fǎng)問(wèn)這個(gè)頂點(diǎn),然后找出這個(gè)結(jié)點(diǎn)的所有未被訪(fǎng)問(wèn)的鄰接點(diǎn),訪(fǎng)問(wèn)完后再訪(fǎng)問(wèn)這些結(jié)點(diǎn)中第一個(gè)鄰接點(diǎn)的所有結(jié)點(diǎn),重復(fù)此方法,直到所有結(jié)點(diǎn)都被訪(fǎng)問(wèn)完為止。網(wǎng)上介紹的文章很多,我這邊就不過(guò)多介紹了;
舉個(gè)例子
var tree = { val: "div", ch: [{ val: "p", ch: [{ val: "text1" }] }, { val: "p", ch: [{ val: "span", ch: [{ val: "tetx2" }] }] }] } function bfs(tree) { var queue = []; var res = [] if (!tree) return queue.push(tree); while (queue.length) { var node = queue.shift(); if (node.ch) { for (var i = 0; i < node.ch.length; i++) { queue.push(node.ch[i]); } } if (node.val) { res.push(node.val); } } return res; } console.log(bfs(tree)) //["div", "p", "p", "text1", "span", "tetx2"]
思路:先把根節(jié)點(diǎn)放入一個(gè)數(shù)組queue中,然后將其取出來(lái),判斷其是否有子節(jié)點(diǎn),如果有,將其子節(jié)點(diǎn)依次放入queue數(shù)組中;然后依次再?gòu)倪@個(gè)數(shù)組中取值,重復(fù)上述步驟,直到這個(gè)數(shù)組queue沒(méi)有數(shù)據(jù);
這里snabbdom會(huì)比較每一個(gè)節(jié)點(diǎn)它的sel是否相似,如果相似對(duì)其子節(jié)點(diǎn)再進(jìn)行比較,否則直接刪除這個(gè)節(jié)點(diǎn),添加新節(jié)點(diǎn),其子節(jié)點(diǎn)也不會(huì)繼續(xù)進(jìn)行比較
patch函數(shù)
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是真實(shí)的dom節(jié)點(diǎn),則轉(zhuǎn)化為一個(gè)空vnode,一般這是初始化渲染的時(shí)候會(huì)用到 if (isUndef(oldVnode.sel)) { oldVnode = emptyNodeAt(oldVnode); } //如果oldvnode與vnode相似,進(jìn)行更新;相似是比較其key值與sel值 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; };
流程圖:
當(dāng)oldvnode的sel為空的時(shí)候,這里出現(xiàn)的場(chǎng)景基本上就是我們第一次調(diào)用patch去初始化渲染頁(yè)面
比較相似的方式為vnode的sel,key兩個(gè)屬性是否相等,不定義key值也沒(méi)關(guān)系,因?yàn)椴欢x則為undefined,而undefined===undefined,只需要sel相等即可相似
由于比較策略是同層級(jí)比較,所以當(dāng)父節(jié)點(diǎn)不相相似時(shí),子節(jié)點(diǎn)也不會(huì)再去比較
最后會(huì)將vnode返回,也就是我們此刻需要渲染到頁(yè)面上的vnode,它將會(huì)作為下一次渲染時(shí)的oldvnode
這基本上就是一個(gè)對(duì)比的大體過(guò)程,值得研究的東西還在后面,涉及到了其核心的diff算法,下篇文章再提。
再介紹一下上面用到的一些功能函數(shù):
isUndef
為is.js中的函數(shù),用來(lái)判斷數(shù)據(jù)是否為undefined
emptyNodeAt
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); }
用來(lái)將一個(gè)真實(shí)的無(wú)子節(jié)點(diǎn)的DOM節(jié)點(diǎn)轉(zhuǎn)化成vnode形式,
如:
sameVnode
function sameVnode(vnode1, vnode2) { return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; }
用來(lái)比較兩個(gè)vnode是否相似。
如果新舊vnode的key和sel都相同,說(shuō)明兩個(gè)vnode相似,我們就可以保留舊的vnode節(jié)點(diǎn),再具體去比較其差異性,在舊的vnode上進(jìn)行"打補(bǔ)丁",否則直接替換節(jié)點(diǎn)。這里需要說(shuō)的是如果不定義key值,則這個(gè)值就為undefined,undefined===undefined //true,所以平時(shí)在用vue的時(shí)候,在沒(méi)有用v-for渲染的組件的條件下,是不需要定義key值的,不會(huì)影響其比較。
createElm
創(chuàng)建vnode對(duì)應(yīng)的真實(shí)dom,并將其賦值給vnode.elm,后續(xù)對(duì)于dom的修改都是在這個(gè)值上進(jìn)行
//將vnode創(chuàng)建為真實(shí)dom function createElm(vnode, insertedVnodeQueue) { var i, data = vnode.data; if (isDef(data)) { //當(dāng)節(jié)點(diǎn)上存在hook而且hook中有beforeCreate鉤子時(shí),先調(diào)用beforeCreate回調(diào),對(duì)剛創(chuàng)建的vnode進(jìn)行處理 if (isDef(i = data.hook) && isDef(i = i.beforeCreate)) { i(vnode); //獲取beforeCreate鉤子修改后的數(shù)據(jù) data = vnode.data; } } var elm, children = vnode.children, sel = vnode.sel; if (isDef(sel)) { //解析sel參數(shù),例如div#divId.divClass ==>id="divId" class="divClass" 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)); } } else if (is.primitive(vnode.text)) { //如果存在子文本節(jié)點(diǎn),則直接將其插入到當(dāng)前Vnode節(jié)點(diǎn) 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)) { //觸發(fā)自身的create鉤子回調(diào) 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; }
patchVnode
如果兩個(gè)vnode相似,則會(huì)對(duì)具體的vnode進(jìn)行‘打補(bǔ)丁’的操作
function patchVnode(oldVnode, vnode, insertedVnodeQueue) { var i, hook; //在patch之前,先調(diào)用vnode.data的beforePatch鉤子 if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.beforePatch)) { i(oldVnode, vnode); } var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children; //如果oldnode和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); } /* 分情況討論節(jié)點(diǎn)的更新: new代表新Vnode old代表舊Vnode ps:如果自身存在文本節(jié)點(diǎn),則不存在子節(jié)點(diǎn) 即:有text則不會(huì)存在ch,反之亦然 1 new不為文本節(jié)點(diǎn) 1.1 new不為文本節(jié)點(diǎn),new還存在子節(jié)點(diǎn) 1.1.1 new不為文本節(jié)點(diǎn),new還存在子節(jié)點(diǎn),old有子節(jié)點(diǎn) 1.1.2 new不為文本節(jié)點(diǎn),new還存在子節(jié)點(diǎn),old沒(méi)有子節(jié)點(diǎn) 1.1.2.1 new不為文本節(jié)點(diǎn),new還存在子節(jié)點(diǎn),old沒(méi)有子節(jié)點(diǎn),old為文本節(jié)點(diǎn) 1.2 new不為文本節(jié)點(diǎn),new不存在子節(jié)點(diǎn) 1.2.1 new不為文本節(jié)點(diǎn),new不存在子節(jié)點(diǎn),old存在子節(jié)點(diǎn) 1.2.2 new不為文本節(jié)點(diǎn),new不存在子節(jié)點(diǎn),old為文本節(jié)點(diǎn) 2.new為文本節(jié)點(diǎn) 2.1 new為文本節(jié)點(diǎn),并且old與new的文本節(jié)點(diǎn)不相等 ps:這里只需要討論這一種情況,因?yàn)槿绻鹢ld存在子節(jié)點(diǎn),那么文本節(jié)點(diǎn)text為undefined,則與new的text不相等 直接node.textContent即可清楚old存在的子節(jié)點(diǎn)。若old存在子節(jié)點(diǎn),且相等則無(wú)需修改 */ //1 if (isUndef(vnode.text)) { //1.1.1 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); } //1.1.2 else if (isDef(ch)) { //oldvnode是text節(jié)點(diǎn),則將elm的text清除 //1.1.2.1 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 //1.2.1 else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } //1.2.2 //如果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, //2.1 else if (oldVnode.text !== vnode.text) { api.setTextContent(elm, vnode.text); } //patch完,觸發(fā)postpatch鉤子 if (isDef(hook) && isDef(i = hook.postpatch)) { i(oldVnode, vnode); } }
removeVnodes
/* 這個(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) * * * @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]; //ch代表子節(jié)點(diǎn) 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); } } } }
invokeDestroyHook
/* 這個(gè)函數(shù)用于手動(dòng)觸發(fā)destory鉤子回調(diào),主要步驟如下: 先調(diào)用vnode上的destory 再調(diào)用全局下的destory 遞歸調(diào)用子vnode的destory */ function invokeDestroyHook(vnode) { var i, j, data = vnode.data; if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode); //調(diào)用自身的destroy鉤子 for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); //調(diào)用全局destroy鉤子 if (isDef(i = vnode.children)) { for (j = 0; j < vnode.children.length; ++j) { invokeDestroyHook(vnode.children[j]); } } } }
addVnodes
//將vnode轉(zhuǎn)換后的dom節(jié)點(diǎn)插入到dom樹(shù)的指定位置中去 function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { for (; startIdx <= endIdx; ++startIdx) { api.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before); } }
createRmCb
/* remove一個(gè)vnode時(shí),會(huì)觸發(fā)remove鉤子作攔截器,只有在所有remove鉤子 回調(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); } }; }
還有一個(gè)最核心的函數(shù)updateChildren,這個(gè)留到下篇文章再說(shuō);
我們這邊簡(jiǎn)單的總結(jié)一下:
對(duì)比渲染的流程大體分為
1.通過(guò)sameVnode來(lái)判斷兩個(gè)vnode是否值得進(jìn)行比較
2.如果不值得,直接刪除舊的vnode,渲染新的vnode
3.如果值得,調(diào)用模塊鉤子函數(shù),對(duì)其節(jié)點(diǎn)的屬性進(jìn)行替換,例如style,event等;再判斷節(jié)點(diǎn)子節(jié)點(diǎn)是否為文本節(jié)點(diǎn),如果為文本節(jié)點(diǎn)則進(jìn)行更替,如果還存在其他子節(jié)點(diǎn)則調(diào)用updateChildren,對(duì)子節(jié)點(diǎn)進(jìn)行更新,更新流程將會(huì)回到第一步,重復(fù);
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/100342.html
摘要:閑聊在學(xué)的過(guò)程中,虛擬應(yīng)該是聽(tīng)的最多的概念之一,得知其是借鑒進(jìn)行開(kāi)發(fā),故習(xí)之。以我的觀(guān)點(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...
摘要:毫無(wú)疑問(wèn)的是算法的復(fù)雜度與效率是決定能夠帶來(lái)性能提升效果的關(guān)鍵因素。速度略有損失,但可讀性大大提高。因此目前的主流算法趨向一致,在主要思路上,與的方式基本相同。在里面實(shí)現(xiàn)了的算法與支持。是唯一添加的方法所以只發(fā)生在中。 VirtualDOM是react在組件化開(kāi)發(fā)場(chǎng)景下,針對(duì)DOM重排重繪性能瓶頸作出的重要優(yōu)化方案,而他最具價(jià)值的核心功能是如何識(shí)別并保存新舊節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)之間差異的方法,...
摘要:這個(gè)大概是的鉤子吧在每一次插入操作的時(shí)候都將節(jié)點(diǎn)這類(lèi)型方法可以看出來(lái)是在調(diào)用對(duì)應(yīng)的方法因?yàn)殚_(kāi)始的時(shí)候就導(dǎo)入進(jìn)來(lái)了插入節(jié)點(diǎn)操作的時(shí)候都需要加入子節(jié)點(diǎn)有子元素也就是的時(shí)候遞歸調(diào)用循環(huán)子節(jié)點(diǎn)生成對(duì)應(yīng)著一些操作之后都要觸發(fā)鉤子函數(shù)。 snabbdom 本文的snabbdom源碼分析采用的是0.54版本(即未用ts重寫(xiě)前的最后一版) 前期了解 snabbdom被用作vue的虛擬dom。本文的一個(gè)...
摘要:總共寫(xiě)了四篇文章都是自己的一些拙見(jiàn),僅供參考,請(qǐng)多多指教,我這邊也會(huì)持續(xù)修正加更新介紹一下基本用法介紹一下渲染原理介紹一下的算法和對(duì)值的認(rèn)識(shí)介紹一下對(duì)于兼容的修改這篇主要是記錄一下針對(duì)做了哪些修改增加用來(lái)兼容某些功能函數(shù),例如等將每個(gè)文件單 總共寫(xiě)了四篇文章(都是自己的一些拙見(jiàn),僅供參考,請(qǐng)多多指教,我這邊也會(huì)持續(xù)修正加更新) 介紹一下snabbdom基本用法 介紹一下snabbdo...
摘要:總共寫(xiě)了四篇文章都是自己的一些拙見(jiàn),僅供參考,請(qǐng)多多指教,我這邊也會(huì)持續(xù)修正加更新介紹一下基本用法介紹一下渲染原理介紹一下的算法和對(duì)值的認(rèn)識(shí)介紹一下對(duì)于兼容的修改這篇主要是說(shuō)一下的算法在上一篇中我總結(jié)過(guò)對(duì)比渲染的流程大體分為通過(guò)來(lái)判斷兩個(gè)是 總共寫(xiě)了四篇文章(都是自己的一些拙見(jiàn),僅供參考,請(qǐng)多多指教,我這邊也會(huì)持續(xù)修正加更新) 介紹一下snabbdom基本用法 介紹一下snabbdo...
閱讀 1450·2021-10-11 10:57
閱讀 2264·2021-09-02 15:15
閱讀 1769·2019-08-30 15:56
閱讀 1316·2019-08-30 15:55
閱讀 1243·2019-08-30 15:44
閱讀 1068·2019-08-29 12:20
閱讀 1438·2019-08-29 11:12
閱讀 1148·2019-08-28 18:29