<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                >[success] # patch 函數 1. `patch` 函數 是通過調用`init`閉包返回的函數,會傳入新老的`Vnode`,進行比較后進行視圖渲染,返回新的` VNode`,作為下一次 **patch() 的 oldVnod** 2. 代碼的執行過程: 2.1. 首先執行模塊中的**鉤子函數 pre** 2.2. 執行`sameVnode(oldVnode, vnode)`比較`oldVnode 和 Vnode`,返回`true`調用 `patchVnode()`,找節點的`差異并更新 DOM`,返回`false`,從`oldVnode`中`elm `獲取**真實渲染dom**,在通過`createElm` 根據新的**Vnode創建出對應dom**,獲取**老dom**位置,將**新dom**插入原來老的位置**并刪除老的dom** ~~~ // init 內部返回 patch函數,把vnode渲染成真實dom,并返回vnode // 不需要額外傳遞modules domapi return function patch(oldVnode: VNode | Element, vnode: VNode): VNode { let i: number, elm: Node, parent: Node; // 保存新插入節點的隊列,為了觸發鉤子函數 const insertedVnodeQueue: VNodeQueue = []; // 執行模塊的 pre 鉤子函數 for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); // 如果 oldVnode 不是 VNode,創建 VNode 并設置 elm if (!isVnode(oldVnode)) { // 把 DOM 元素轉換成空的 VNode oldVnode = emptyNodeAt(oldVnode); } // 如果新舊節點是相同節點(key 和 sel 相同) if (sameVnode(oldVnode, vnode)) { // 找節點的差異并更新 DOM patchVnode(oldVnode, vnode, insertedVnodeQueue); } else { // 如果新舊節點不同,vnode 創建對應的 DOM // 獲取當前的 DOM 元素 elm = oldVnode.elm!; parent = api.parentNode(elm); // 觸發 init/create 鉤子函數,創建 DOM createElm(vnode, insertedVnodeQueue); if (parent !== null) { // 如果父節點不為空,把 vnode 對應的 DOM 插入到文檔中 api.insertBefore(parent, vnode.elm!, api.nextSibling(elm)); // 移除老節點 removeVnodes(parent, [oldVnode], 0, 0); } } // 執行用戶設置的 insert 鉤子函數 for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]); } // 執行模塊的 post 鉤子函數 for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); // 返回 vnode return vnode; }; ~~~ * 簡化這個模型代碼,實際只需要做的`patch` 的主要功能是比對兩個 `VNode `節點,**將「差異」更新到視圖上** ~~~ function patch (oldVnode, vnode, parentElm) { if (!oldVnode) { addVnodes(parentElm, null, vnode, 0, vnode.length - 1); } else if (!vnode) { removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1); } else { if (sameVnode(oldVNode, vnode)) { // 相同開始diff 算法比較 patchVnode(oldVNode, vnode); } else { // 不同直接刪除舊的,增加新節點。 removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1); addVnodes(parentElm, null, vnode, 0, vnode.length - 1); } } } ~~~ >[danger] ##### 幾個工具函數說明 * `sameVnode`函數比較新老虛擬dom(Vnode)是否相同,比較的幾個點,`key`,`sel`即選擇器比較創**建元素節點**,`is`是**string**類型作為判斷是否是自定義元素 ~~~ function sameVnode(vnode1: VNode, vnode2: VNode): boolean { const isSameKey = vnode1.key === vnode2.key; const isSameIs = vnode1.data?.is === vnode2.data?.is; const isSameSel = vnode1.sel === vnode2.sel; const isSameTextOrFragment = !vnode1.sel && vnode1.sel === vnode2.sel ? typeof vnode1.text === typeof vnode2.text : true; return isSameSel && isSameKey && isSameIs && isSameTextOrFragment; } ~~~ * `emptyNodeAt` 函數將真實dom 轉換為Vnode節點,為了進行Vnode 新老比較,回顧一下`Vnode`結構`sel,data,children,text,elm`,elm記錄的就是真實dom,將真實dom 屬性依次對應傳入到Vnode 函數中 ~~~ function emptyNodeAt (elm: Element) { const id = elm.id ? '#' + elm.id : '' const c = elm.className ? '.' + elm.className.split(' ').join('.') : '' return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm) } ~~~ * `createElm`函數作用,將虛擬 dom 轉變為真實dom元素,插入回`Vnode`對象的`elm`屬性,此時`Vnode`既有**js對象表現形式**,并且表現形式中`elm `屬性記錄了**真實dom** 整段代碼執行順序 1. 首先觸發 `vnode` 中 `init` 鉤子函數 ~~~ h( 'div', { hook: { init() { console.log(1111111111) // 執行后控制臺打印 }, }, }, [(h('span', '子節點'), 1, 23, 34)] ) ~~~ 2. 開始解析 `h` 函數中`sel `參數,sel參數設置可能是**選擇器字符串**`div#container.cls`可能是**單純的標簽**`div`,有可能是 `!`**評論節點**,或者是沒有`sel`參數 只是**單純的文本節點**,因此接下來就是對這些標簽分析轉換成對應真實`dom` ~~~ // 整體代碼流程 // 解析選擇器,設置標簽的 id 和 class 屬性 // 執行模塊的 create 鉤子函數 // 如果 vnode 有 children,創建子 vnode 對應的 DOM,追加到 DOM 樹 // 如果 vnode 的 text 值是 string/number,創建文本節點并追擊到 DOM 樹 // 行用戶設置的 create 鉤子函數 // 如果有用戶設置的 insert 鉤子函數,把 vnode 添加到隊列中 function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node { let i: any, data = vnode.data; if (data !== undefined) { // 執行用戶設置的 init 鉤子函數 const init = data.hook ? .init; if (isDef(init)) { init(vnode); data = vnode.data; } } let children = vnode.children, sel = vnode.sel; if (sel === '!') { // 如果選擇器是!,創建評論節點 if (isUndef(vnode.text)) { vnode.text = ''; } vnode.elm = api.createComment(vnode.text!); } else if (sel !== undefined) { // 如果選擇器不為空 // 解析選擇器 // Parse selector 如果是選擇器字符串div#container.cls,需要獲取sel 標簽(div) let vnode = h('div#container.cls', 'hello word') const hashIdx = sel.indexOf('#'); const dotIdx = sel.indexOf('.', hashIdx); const hash = hashIdx > 0 ? hashIdx : sel.length; const dot = dotIdx > 0 ? dotIdx : sel.length; const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; const elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag) : api.createElement(tag); // 在這之前都是對虛擬dom 的sel屬性進行解析,為了對提供的sel 選擇器而創建dom準備的 // 注意創建真實dom 有兩種一種是createElementNS這是創建svg,一種是createElement 創建普通dom // --------------------------------------------- if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot)); if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' ')); // 執行模塊的 create 鉤子函數 for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); // 如果 vnode 中有子節點,創建子 vnode 對應的 DOM 元素并追加到 DOM 樹上 if (is.array(children)) { for (i = 0; i < children.length; ++i) { const ch = children[i]; if (ch != null) { api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue)); } } } else if (is.primitive(vnode.text)) { // 如果 vnode 的 text 值是 string/number,創建文本節點并追加到 DOM 樹 api.appendChild(elm, api.createTextNode(vnode.text)); } const hook = vnode.data!.hook; if (isDef(hook)) { // 執行用戶傳入的鉤子 create hook.create ? .(emptyNode, vnode); if (hook.insert) { // 把 vnode 添加到隊列中,為后續執行 insert 鉤子做準備 insertedVnodeQueue.push(vnode); } } } else { // 如果選擇器為空,創建文本節點 vnode.elm = api.createTextNode(vnode.text!); } // 返回新創建的 DOM return vnode.elm; } ~~~ * 其他幫助函數 ~~~ function addVnodes(parentElm: Node, before: Node | null, vnodes: Array<VNode>, startIdx: number, endIdx: number, insertedVnodeQueue: VNodeQueue) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx]; if (ch != null) { api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); } } } function removeVnodes(parentElm: Node, vnodes: Array<VNode>, startIdx: number, endIdx: number): void { for (; startIdx <= endIdx; ++startIdx) { let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx]; if (ch != null) { // 如果 sel 有值 if (isDef(ch.sel)) { // 執行 destroy 鉤子函數(會執行所有子節點的 destroy 鉤子函數) invokeDestroyHook(ch); listeners = cbs.remove.length + 1; // 創建刪除的回調函數 rm = createRmCb(ch.elm as Node, listeners); for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); // 執行用戶設置的 remove 鉤子函數 if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) { i(ch, rm); } else { // 如果沒有用戶鉤子函數,直接調用刪除元素的方法 rm(); } } else { // Text node // 如果是文本節點,直接調用刪除元素的方法 api.removeChild(parentElm, ch.elm as Node); } } } } function invokeDestroyHook(vnode: VNode) { let i: any, j: number, data = vnode.data; if (data !== undefined) { // 執行用戶設置的 destroy 鉤子函數 if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode); // 調用模塊的 distroy 鉤子函數 for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); // 執行子節點的 distroy 鉤子函數 if (vnode.children !== undefined) { for (j = 0; j < vnode.children.length; ++j) { i = vnode.children[j]; if (i != null && typeof i !== "string") { invokeDestroyHook(i); } } } } } function createRmCb(childElm: Node, listeners: number) { // 返回刪除元素的回調函數 return function rmCb() { if (--listeners === 0) { const parent = api.parentNode(childElm); api.removeChild(parent, childElm); } }; } ~~~ >[info] ## patchVnode `patchVnode` 函數作為`patch`函數中新老`Vnode`進行比較依靠的是屬性的`標簽和key`比較相同可以觸發 `patchVnode` 函數,找節點的**差異并更新 DOM** ~~~ function patchVnode( oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue ) { const hook = vnode.data?.hook hook?.prepatch?.(oldVnode, vnode) const elm = (vnode.elm = oldVnode.elm)! if (oldVnode === vnode) return if ( vnode.data !== undefined || (isDef(vnode.text) && vnode.text !== oldVnode.text) ) { vnode.data ??= {} oldVnode.data ??= {} for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) vnode.data?.hook?.update?.(oldVnode, vnode) } const oldCh = oldVnode.children as VNode[] const ch = vnode.children as VNode[] // 如果 vnode.text 未定義,有text 就沒有children if (isUndef(vnode.text)) { // 如果新老節點都有 children if (isDef(oldCh) && isDef(ch)) { // 使用 diff 算法對比子節點,更新子節點 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) } else if (isDef(ch)) { // 如果新節點有 children,老節點沒有 children // 如果老節點有text,清空dom 元素的內容 if (isDef(oldVnode.text)) api.setTextContent(elm, '') // 批量添加子節點 addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { // 如果老節點有children,新節點沒有children // 批量移除子節點 removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { // 如果老節點有 text,清空 DOM 元素 api.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { // 如果沒有設置 vnode.text if (isDef(oldCh)) { // 如果老節點有 children,移除 removeVnodes(elm, oldCh, 0, oldCh.length - 1) } // 設置 DOM 元素的 textContent 為 vnode.text api.setTextContent(elm, vnode.text!) } hook?.postpatch?.(oldVnode, vnode) } ~~~ 代碼中在開始時有兩行代碼,`elm`之前說過是`Vnode `一個屬性,用來記錄`Vnode `真實`dom`的對象,先將老`Vnode`已將創建出來的真實`dom`賦值給新`Vnode `雖然此時并不確認新老相等但先進行賦值操作,如果后續中發現新老Vnode 相等此時不需要做任何改變了,直接 return 掉 ~~~ const elm = (vnode.elm = oldVnode.elm)!; if (oldVnode === vnode) return; ~~~ * 如果不想等進入比較階段,比較其實分兩個情況,就是新`vnode`是文本節點(文本節點沒有什么特點比較情況,相同不變,不同更改為新的 `vnode` 文本),新`vnode`是非文本類型 ~~~ // 如果 vnode.text 未定義,有text 就沒有children if (isUndef(vnode.text)) { // ... 非文本情況 } else if (oldVnode.text !== vnode.text) { // .... 是文本的情況 // 如果沒有設置 vnode.text if (isDef(oldCh)) { // 如果老節點有 children,移除 removeVnodes(elm, oldCh, 0, oldCh.length - 1) } // 設置 DOM 元素的 textContent 為 vnode.text api.setTextContent(elm, vnode.text!) } ~~~ * `oldCh`與`ch`都存在且不相同時,使用`updateChildren`函數來更新子節點,使用 `diff` 算法對比子節點,更新子節點 * 如果只有`ch`存在的時候,如果老節點是文本節點則先將節點的文本清除,然后將`ch`批量插入插入到節點elm下 * 同理當只有`oldch`存在時,說明需要將老節點通過`removeVnodes`全部清除 * 只有老節點是文本節點的時候,清除其節點文本內容 ~~~ if (oldCh && ch && (oldCh !== ch)) { updateChildren(elm, oldCh, ch); } else if (ch) { if (oldVnode.text) nodeOps.setTextContent(elm, ''); addVnodes(elm, null, ch, 0, ch.length - 1); } else if (oldCh) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (oldVnode.text) { nodeOps.setTextContent(elm, '') } ~~~ >[danger] ##### updateChildren -- diff `updateChildren ` 是真正的`diff`發生區域,能進入`patchVnode `函數都是新老父節點相同的,接下來要比較的其實是新老子節點,如果新老子節點形式不同(例如我是文本你是dom元素),但如果新老子節點形式相同要做就是對應位置比較,即如何性能最優的去比較 * 最笨的方法拿每一個老節點虛擬`dom`和新節點的去比較,如下圖嵌套三層的情況下時間復雜度為 `O(n^3)` ![](https://img.kancloud.cn/b5/79/b579c580b83dd523f6a3caee2ad27d81_599x280.png) * 實際采用的方法**利用指針進行找同級別的子節點依次比較**,然后再找下一級別的節點比較,這樣算法的 時間復 雜度為 `O(n)`,考慮的就是dom復用,有時候重新排序只是新老dom節點位置發生變化,如果能**復用相同的dom而不是重新創建dom**這樣就又可以減少一部分性能開銷 `oldStartVnode / newStartVnode` (舊開始節點 / 新開始節點) `oldEndVnode / newEndVnode` (舊結束節點 / 新結束節點) `oldStartVnode / oldEndVnode` (舊開始節點 / 新結束節點) `oldEndVnode / newStartVnode` (舊結束節點 / 新開始節點) `oldStartIdx`、`newStartIdx`、`oldEndIdx`以及`newEndIdx`兩兩比對的過程,一共會出現 2\*2=4 * **這四種其實對應下圖四種情況他們找到能復用的vnode節點**,當在相同時候會在調用`patchVnode`函數 然后觸發`if (oldVnode === vnode) return` 這里 ~~~ else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode); nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode); nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } ~~~ ![](https://img.kancloud.cn/7d/ae/7dae086ded79545de9f358d61a10677d_2600x4182.png) [參考視頻動畫鏈接](https://www.bilibili.com/video/BV1b5411V7i3?t=479) 第五種情況最復雜,第五種沒有規律,但是存在可以復用的節點,使用新開始節點的key值在【舊開始節點-舊結束節點】的子數組中找相同key的節點。若沒有在子數組中找到相同key節點則新開始節點是新節點,創建一個該節點的真實DOM節點插入到舊開始節點指向的真實DOM節點之前;若找到相同key的節點獲取該舊節點,比較新、舊節點的選擇器是否相同,選擇器不同則創建一個該節點的真實DOM節點插入到舊開始節點指向的真實DOM節點之前,選擇器相同則對比兩個節點的差異執行相應的操作(如更新DOM節點),并將子數組中key節點指向到真實DOM節點移動到到舊開始索引指向的真實DOM節點之前(移動的是真實DOM而不是虛擬DOM,虛擬DOM無變化)。最后將新開始索引指向下一個節點 ~~~ function updateChildren (parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx: KeyToIndexMap | undefined let idxInOld: number let elmToMove: VNode let before: any while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 索引變化后,可能會把節點設置為空 if (oldStartVnode == null) { // 節點為空移動索引 oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left } else if (oldEndVnode == null) { oldEndVnode = oldCh[--oldEndIdx] } else if (newStartVnode == null) { newStartVnode = newCh[++newStartIdx] } else if (newEndVnode == null) { newEndVnode = newCh[--newEndIdx] // 比較開始和結束節點的四種情況 } else if (sameVnode(oldStartVnode, newStartVnode)) { // 1. 比較老開始節點和新開始節點 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { // 2.比較老結束節點和新的結束節點 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right // 3.比較老開始節點和新的結束節點 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 // 4.比較老結束節點和新的開始節點 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { // 開始節點和結束節點都不相同 // 使用newStartNode 的key在老節點數組中找相同節點 // 先設置記錄 key 和index對象 if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) } // 遍歷 newStartVnode,從舊節點中找相同key的oldVnode的索引 idxInOld = oldKeyToIdx[newStartVnode.key as string] // 如果是新的vnode if (isUndef(idxInOld)) { // New element // 如果沒找到,newStartNode 是新節點 // 創建元素插入DOM樹 api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!) } else { // 如果找到相同key的老節點,記錄到elmToMove遍歷 elmToMove = oldCh[idxInOld] if (elmToMove.sel !== newStartVnode.sel) { // 如果新舊節點的選擇器不同 // 創建新開始節點對應的DOM元素,插入到DOM樹種 api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!) } else { // 如果相同,patchVnode() // 把elmToMove 對應的DOM元素,移動到左邊 patchVnode(elmToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined as any api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!) } } // 重新給newStartVnode 復制,指向下一個新節點 newStartVnode = newCh[++newStartIdx] } } // 循環結束,老節點數組先遍歷完成或者新節點數組先遍歷完成 if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) { if (oldStartIdx > oldEndIdx) { // 如果老節點數組先遍歷完成,說明有新的節點剩余 // 把剩余的新節點都插入到右邊 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else { // 如果新節點數組先遍歷完成,說明老節點有剩余 removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } } } ~~~ >[info] ## key 作用 `Key `一般用于生成一列同類型節點時使用,這種情況下,當修改這些同類型節點的某個內容、變更位置、刪除、添加等時,此時界面需要更新視圖,調用 `patch `方法通過對比**新、舊節點的變化來更新視圖**。其從根節點開始若**新、舊 VNode 相同**,則調用`patchVnode` `patchVnode `中若新節點沒有文本,且新節點和舊節點都有有子點,則需對子節點進行 `Diff `操作,即調用` updateChildren`,`Key` 就在 `updateChildren `起了大作用 `updateChildren `中會遍歷對比上步中的新、舊節點的子節點,并按 Diff 算法通過 sameVnode 來判斷要對比的節點是否相同 **若這里的子節點未設置 Key**,則此時的每個新、舊子節點在執行`sameVnode `時會判定相同,然后再次執行一次 patchVnode 來對比這些子節點的子節點 **若設置了 Key**,當執行 sameVnode **若 Key 不同 sameVnode 返回 false**,然后執行后續判斷; **若 Key 相同 sameVnode 返回 true**,然后再執行 patchVnode 來對比這些子節點的子節點 即,使用了 Key 后,可以優化新、舊節點的對比判斷,減少了遍歷子節點的層次,少使用很多次 `patchVnode`,在第五種情況最明顯,會在老Vnode中找到新Vnode 能復用的節點直接使用,這樣就避免了重新渲染真實dom的開銷
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看