<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智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] <br> <br> # 模板轉換成視圖的過程 * Vue.js通過編譯將template 模板轉換成渲染函數(render ) ,執行渲染函數就可以得到一個虛擬節點樹 * 在對 Model 進行操作的時候,會觸發對應 Dep 中的 Watcher 對象。Watcher 對象會調用對應的 update 來修改視圖。這個過程主要是將新舊虛擬節點進行差異對比,然后根據對比結果進行DOM操作來更新視圖。 <br> 簡單點講,在Vue的底層實現上,Vue將模板編譯成虛擬DOM渲染函數。結合Vue自帶的響應系統,在狀態改變時,Vue能夠智能地計算出重新渲染組件的最小代價并應到DOM操作上。 <br> ![](https://user-gold-cdn.xitu.io/2019/6/26/16b9230a4c3cdd40?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 我們先對上圖幾個概念加以解釋: * **渲染函數**:渲染函數是用來生成Virtual DOM的。Vue推薦使用模板來構建我們的應用界面,在底層實現中Vue會將模板編譯成渲染函數,當然我們也可以不寫模板,直接寫渲染函數,以獲得更好的控制。 * **VNode 虛擬節點**:它可以代表一個真實的 dom 節點。通過 createElement 方法能將 VNode 渲染成 dom 節點。簡單地說,vnode可以理解成**節點描述對象**,它描述了應該怎樣去創建真實的DOM節點。 * **patch(也叫做patching算法)**:虛擬DOM最核心的部分,它可以將vnode渲染成真實的DOM,這個過程是對比新舊虛擬節點之間有哪些不同,然后根據對比結果找出需要更新的的節點進行更新。這點我們從單詞含義就可以看出, patch本身就有補丁、修補的意思,其實際作用是在現有DOM上進行修改來實現更新視圖的目的。Vue的Virtual DOM Patching算法是基于[Snabbdom](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fsnabbdom%2Fsnabbdom "https://github.com/snabbdom/snabbdom")的實現,并在些基礎上作了很多的調整和改進。 <br> <br> # Virtual DOM ## Virtual DOM 是什么? Virtual DOM 其實就是一棵以 JavaScript 對象( VNode 節點)作為基礎的樹,用對象屬性來描述節點,實際上它只是一層對真實 DOM 的抽象。最終可以通過一系列操作使這棵樹映射到真實環境上。 <br> 簡單來說,可以把Virtual DOM 理解為一個簡單的JS對象,并且最少包含標簽名( tag)、屬性(attrs)和子元素對象( children)三個屬性。不同的框架對這三個屬性的命名會有點差別。 <br> 對于虛擬DOM,咱們來看一個簡單的實例,就是下圖所示的這個,詳細的闡述了`模板 → 渲染函數 → 虛擬DOM樹 → 真實DOM`的一個過程 ![image.png](https://user-gold-cdn.xitu.io/2019/6/25/16b8a4e46453e643?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> <br> ## Virtual DOM 作用是什么? **虛擬DOM的最終目標是將虛擬節點渲染到視圖上**。但是如果直接使用虛擬節點覆蓋舊節點的話,會有很多不必要的DOM操作。例如,一個ul標簽下很多個li標簽,其中只有一個li有變化,這種情況下如果使用新的ul去替代舊的ul,因為這些不必要的DOM操作而造成了性能上的浪費。 <br> 為了避免不必要的DOM操作,虛擬DOM在虛擬節點映射到視圖的過程中,將虛擬節點與上一次渲染視圖所使用的舊虛擬節點(oldVnode)做對比,找出真正需要更新的節點來進行DOM操作,從而避免操作其他無需改動的DOM。 <br> 其實虛擬DOM在Vue.js主要做了兩件事: * 提供與真實DOM節點所對應的虛擬節點vnode * 將虛擬節點vnode和舊虛擬節點oldVnode進行對比,然后更新視圖 <br> <br> ## 為何需要Virtual DOM? * 具備跨平臺的優勢 由于 Virtual DOM 是以 JavaScript 對象為基礎而不依賴真實平臺環境,所以使它具有了跨平臺的能力,比如說瀏覽器平臺、Weex、Node 等。 <br> * 操作 DOM 慢,js運行效率高。我們可以將DOM對比操作放在JS層,提高效率。 因為DOM操作的執行速度遠不如Javascript的運算速度快,因此,把大量的DOM操作搬運到Javascript中,運用patching算法來計算出真正需要更新的節點,最大限度地減少DOM操作,從而顯著提高性能。 <br> Virtual DOM 本質上就是在 JS 和 DOM 之間做了一個緩存。可以類比 CPU 和硬盤,既然硬盤這么慢,我們就在它們之間加個緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內存(Virtual DOM),最后的時候再把變更寫入硬盤(DOM) <br> * 提升渲染性能 Virtual DOM的優勢不在于單次的操作,而是在大量、頻繁的數據更新下,能夠對視圖進行合理、高效的更新。 為了實現高效的DOM操作,一套高效的虛擬DOM diff算法顯得很有必要。**我們通過patch 的核心----diff 算法,找出本次DOM需要更新的節點來更新,其他的不更新**。那diff 算法的實現過程是怎樣的? <br> ## 實現 vnode **(1)如何用 `JS` 對象模擬 `DOM` 樹** 例如一個真實的 `DOM` 節點如下: ~~~ <div id="virtual-dom"> <p>Virtual DOM</p> <ul id="list"> <li class="item">Item 1</li> <li class="item">Item 2</li> <li class="item">Item 3</li> </ul> <div>Hello World</div> </div> 復制代碼 ~~~ 我們用 `JavaScript` 對象來表示 `DOM` 節點,使用對象的屬性記錄節點的類型、屬性、子節點等。 `element.js` 中表示節點對象代碼如下: ~~~ /** * Element virdual-dom 對象定義 * @param {String} tagName - dom 元素名稱 * @param {Object} props - dom 屬性 * @param {Array<Element|String>} - 子節點 */ function Element(tagName, props, children) { this.tagName = tagName this.props = props this.children = children // dom 元素的 key 值,用作唯一標識符 if(props.key){ this.key = props.key } var count = 0 children.forEach(function (child, i) { if (child instanceof Element) { count += child.count } else { children[i] = '' + child } count++ }) // 子元素個數 this.count = count } function createElement(tagName, props, children){ return new Element(tagName, props, children); } module.exports = createElement; ~~~ 根據 `element` 對象的設定,則上面的 `DOM` 結構就可以簡單表示為: ~~~ var el = require("./element.js"); var ul = el('div',{id:'virtual-dom'},[ el('p',{},['Virtual DOM']), el('ul', { id: 'list' }, [ el('li', { class: 'item' }, ['Item 1']), el('li', { class: 'item' }, ['Item 2']), el('li', { class: 'item' }, ['Item 3']) ]), el('div',{},['Hello World']) ]) ~~~ 現在 `ul` 就是我們用 `JavaScript` 對象表示的 `DOM` 結構,我們輸出查看 `ul` 對應的數據結構如下: ![](https://user-gold-cdn.xitu.io/2019/7/23/16c1e14fcff074f0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) **(2)渲染用 `JS` 表示的 `DOM` 對象** 但是頁面上并沒有這個結構,下一步我們介紹如何將 `ul` 渲染成頁面上真實的 `DOM` 結構,相關渲染函數如下: ~~~ /** * render 將virdual-dom 對象渲染為實際 DOM 元素 */ Element.prototype.render = function () { var el = document.createElement(this.tagName) var props = this.props // 設置節點的DOM屬性 for (var propName in props) { var propValue = props[propName] el.setAttribute(propName, propValue) } var children = this.children || [] children.forEach(function (child) { var childEl = (child instanceof Element) ? child.render() // 如果子節點也是虛擬DOM,遞歸構建DOM節點 : document.createTextNode(child) // 如果字符串,只構建文本節點 el.appendChild(childEl) }) return el } ~~~ 我們通過查看以上 `render` 方法,會根據 `tagName` 構建一個真正的 `DOM` 節點,然后設置這個節點的屬性,最后遞歸地把自己的子節點也構建起來。 我們將構建好的 `DOM` 結構添加到頁面 `body` 上面,如下: ~~~ ulRoot = ul.render(); document.body.appendChild(ulRoot); 復制代碼 ~~~ 這樣,頁面 `body` 里面就有真正的 `DOM` 結構,效果如下圖所示: ![](https://user-gold-cdn.xitu.io/2019/7/23/16c1e179a748d425?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> <br> # diff 算法 ![diff 算法](https://user-gold-cdn.xitu.io/2019/6/23/16b82599955f3694?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) Vue的diff算法是基于snabbdom改造過來的,**僅在同級的vnode間做diff,遞歸地進行同級vnode的diff,最終實現整個DOM樹的更新**。因為跨層級的操作是非常少的,忽略不計,這樣時間復雜度就從O(n3)變成O(n)。 <br> diff 算法包括幾個步驟: * 用 JavaScript 對象結構表示 DOM 樹的結構;然后用這個樹構建一個真正的 DOM 樹,插到文檔當中 * 當狀態變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異 * 把所記錄的差異應用到所構建的真正的DOM樹上,視圖就更新了 ![image.png](https://user-gold-cdn.xitu.io/2019/6/24/16b8a2b9e4725e5a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> ## diff 過程 <br> ![img](https://user-gold-cdn.xitu.io/2017/9/18/ed3fe3ef6c580e16711c39159ce87cd4?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 首先,在新老兩個VNode節點的左右頭尾兩側都有一個變量標記,在遍歷過程中這幾個變量都會向中間靠攏。當oldStartIdx <= oldEndIdx或者newStartIdx <= newEndIdx時結束循環。 <br> 索引與VNode節點的對應關系: oldStartIdx => oldStartVnode oldEndIdx => oldEndVnode newStartIdx => newStartVnode newEndIdx => newEndVnode <br> 在遍歷中,如果存在key,并且滿足sameVnode,會將該DOM節點進行復用,否則則會創建一個新的DOM節點。 <br> 首先,oldStartVnode、oldEndVnode與newStartVnode、newEndVnode兩兩比較一共有2\*2=4種比較方法。 <br> 當新老VNode節點的start或者end滿足sameVnode時,也就是sameVnode(oldStartVnode, newStartVnode)或者sameVnode(oldEndVnode, newEndVnode),直接將該VNode節點進行patchVnode即可。 <br> ![img](https://user-gold-cdn.xitu.io/2017/9/18/dbf1c71d42eaddc6de60301aad17c860?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 如果oldStartVnode與newEndVnode滿足sameVnode,即sameVnode(oldStartVnode, newEndVnode)。 <br> 這時候說明oldStartVnode已經跑到了oldEndVnode后面去了,進行patchVnode的同時還需要將真實DOM節點移動到oldEndVnode的后面。 <br> ![img](https://user-gold-cdn.xitu.io/2017/9/18/0b5beb1c771c3965a77c787fe55a3b57?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 如果oldEndVnode與newStartVnode滿足sameVnode,即sameVnode(oldEndVnode, newStartVnode)。 這說明oldEndVnode跑到了oldStartVnode的前面,進行patchVnode的同時真實的DOM節點移動到了oldStartVnode的前面。 ![img](https://user-gold-cdn.xitu.io/2017/9/18/dc9a1e0b27411b2585960e971559382f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 如果以上情況均不符合,則通過createKeyToOldIdx會得到一個oldKeyToIdx,里面存放了一個key為舊的VNode,value為對應index序列的哈希表。從這個哈希表中可以找到是否有與newStartVnode一致key的舊的VNode節點,如果同時滿足sameVnode,patchVnode的同時會將這個真實DOM(elmToMove)移動到oldStartVnode對應的真實DOM的前面。 ![img](https://user-gold-cdn.xitu.io/2017/9/18/ed03e90b708939205236225c582e26fb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 當然也有可能newStartVnode在舊的VNode節點找不到一致的key,或者是即便key相同卻不是sameVnode,這個時候會調用createElm創建一個新的DOM節點。 ![img](https://user-gold-cdn.xitu.io/2017/9/18/73241a7ea0b6f52c0df4d835a827f3b4?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 到這里循環已經結束了,那么剩下我們還需要處理多余或者不夠的真實DOM節點。 <br> 1.當結束時oldStartIdx > oldEndIdx,這個時候老的VNode節點已經遍歷完了,但是新的節點還沒有。說明了新的VNode節點實際上比老的VNode節點多,也就是比真實DOM多,需要將剩下的(也就是新增的)VNode節點插入到真實DOM節點中去,此時調用addVnodes(批量調用createElm的接口將這些節點加入到真實DOM中去)。 ![img](https://user-gold-cdn.xitu.io/2017/9/18/22369d39d970155963bd71a1370e9b07?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 2。同理,當newStartIdx > newEndIdx時,新的VNode節點已經遍歷完了,但是老的節點還有剩余,說明真實DOM節點多余了,需要從文檔中刪除,這時候調用removeVnodes將這些多余的真實DOM刪除。 ![img](https://user-gold-cdn.xitu.io/2017/9/18/c067fa75aa884a2c231d940de35ef7a1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> <br> # patch patch將新老VNode節點進行比對,然后將根據兩者的比較結果進行最小單位地修改視圖,而不是將整個視圖根據新的VNode重繪。patch的核心在于diff算法,這套算法可以高效地比較viturl dom的變更,得出變化以修改視圖。 <br> 那么patch如何工作的呢? <br> 首先說一下patch的核心diff算法,diff算法是通過同層的樹節點進行比較而非對樹進行逐層搜索遍歷的方式,所以時間復雜度只有O(n),是一種相當高效的算法。 <br> ![img](https://user-gold-cdn.xitu.io/2017/9/18/599392157760360fa2c45895fe3438e9?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> ![img](https://user-gold-cdn.xitu.io/2017/9/18/ebc9bc6e6792591c8a716c9ed876f87d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) <br> 著兩張圖代表舊的VNode與新VNode進行patch的過程,他們只是在同層級的VNode之間進行比較得到變化(第二張圖中相同顏色的方塊代表互相進行比較的VNode節點),然后修改變化的視圖,所以十分高效。
                  <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>

                              哎呀哎呀视频在线观看