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