最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue的diff算法原理你真的了解嗎

 更新時間:2022年03月17日 16:45:57   作者:darabiuz  
這篇文章主要為大家詳細介紹了Vue的diff算法原理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

思維導(dǎo)圖

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

0. 從常見問題引入

  • 虛擬dom是什么?
  • 如何創(chuàng)建虛擬dom?
  • 虛擬dom如何渲染成真是dom?
  • 虛擬dom如何patch(patch)
  • 虛擬DOM的優(yōu)勢?(性能)
  • Vue中的key到底有什么用,為什么不能用index?
  • Vue中的diff算法實現(xiàn)
  • diff算法是深度還是廣度優(yōu)先遍歷

1. 生成虛擬dom

1. h方法實現(xiàn)

virtual dom ,也就是虛擬節(jié)點

1.它通過js的Object對象模擬dom中的節(jié)點

2.再通過特定的render方法將其渲染成真實的dom節(jié)點

eg:

<div id="wrapper" class="1">
    <span style="color:red">hello</span>
    world
</div> 

如果利用h方法生成虛擬dom的話:

h('div', { id: 'wrapper', class: '1' }, h('span', { style: { color: 'red' } }, 'hello'), 'world');

對應(yīng)的js對象如下:

let vd = {
    type: 'div',
    props: { id: 'wrapper', class: '1' },
    children: [
        {
            type: 'span',
            props: { color: 'red' },
            children: [{}]
        },
        {
            type: '',
            props: '',
            text: 'world'
        }
    ]
}

自己實現(xiàn)一個h方法

 function createElement(type, props = {}, ...children) {
    // 防止沒有傳值的話就賦值一個初始值
    let key;
    if (props.key) {
        key = props.key
        delete props.key
    }
    // 如果孩子節(jié)點有字符串類型的,也需要轉(zhuǎn)化為虛擬節(jié)點
    children = children.map(child => {
        if (typeof child === 'string') {
            // 把不是節(jié)點類型的子節(jié)點包裝為虛擬節(jié)點
            return vNode(undefined, undefined, undefined, undefined, child)
        } else {
            return child
        }
    })
    return vNode(type, props, key, children)
}
function vNode(type, props, key, children, text = undefined) {
    return {
        type,
        props,
        key,
        children,
        text
    }
}

2. render方法實現(xiàn)

render的作用:把虛擬dom轉(zhuǎn)化為真實dom渲染到container容器中去

export function render(vnode, container) {
    let ele = createDomElementFrom(vnode) //通過這個方法轉(zhuǎn)換真實節(jié)點
    if (ele) container.appendChild(ele)
}

把虛擬dom轉(zhuǎn)化為真實dom,插入到容器中,如果虛擬dom對象包含type值,說明為元素(createElement),否則為節(jié)點類型(createTextnode),并把真實節(jié)點賦值給虛擬節(jié)點,建立起兩者之間的關(guān)系

function createDomElementFrom(vnode) {
    let { type, key, props, children, text } = vnode
    if (type) {//說明是一個標(biāo)簽
        // 1. 給虛擬元素加上一個domElemnt屬性,建立真實和虛擬dom的聯(lián)系,后面可以用來跟新真實dom
        vnode.domElement = document.createElement(type)
        // 2. 根據(jù)當(dāng)前虛擬節(jié)點的屬性,去跟新真實dom的值
        updateProperties(vnode)
        // 3. children中方的也是一個個的虛擬節(jié)點(就是遞歸把兒子追加到當(dāng)前元素里)
        children.forEach(childVnode => render(childVnode, vnode.domElement))
    } else {//說明是一個文本
    }
    return vnode.domElement

}

function updateProperties(newVnode, oldProps = {}) {
    let domElement = newVnode.domElement //真實dom,
    let newProps = newVnode.props; //當(dāng)前虛擬節(jié)點中的屬性
    // 如果老的里面有,新的里面沒有,說明這個屬性被移出了
    for (let oldPropName in oldProps) {
        if (!newProps[oldPropName]) {
            delete domElement[oldPropName] //新的沒有,為了復(fù)用這個dom,直接刪除
        }
    }
    // 如果新的里面有style,老的里面也有style,style可能還不一樣
    let newStyleObj = newProps.style || {}
    let oldStyleObj = oldProps.style || {}
    for (let propName in oldStyleObj) {
        if (!newStyleObj[propName]) {
            domElement.style[propName] = ''
        }
    }
    // 老的里面沒有,新的里面有
    for (let newPropsName in newProps) {
        // 直接用新節(jié)點的屬性覆蓋老節(jié)點的屬性
        if (newPropsName === 'style') {
            let styleObj = newProps.style;
            for (let s in styleObj) {
                domElement.style[s] = styleObj[s]
            }
        } else {
            domElement[newPropsName] = newProps[newPropsName]
        }
    }

}

根據(jù)當(dāng)前虛擬節(jié)點的屬性,去更新真實dom的值
由于還有子節(jié)點,所以還需要遞歸,生成子節(jié)點虛擬dom的真實節(jié)點,插入當(dāng)前的真實節(jié)點里去

在這里插入圖片描述

3. 再次渲染

剛剛可能會有點不解,為什么要把新的節(jié)點和老的節(jié)點屬性進行比對,因為剛剛是首次渲染,現(xiàn)在講一下二次渲染

比如說現(xiàn)在構(gòu)建了一個新節(jié)點newNode,我們需要和老節(jié)點進行對比,然而并不是簡單的替換,而是需要盡可能多地進行復(fù)用

首先判斷父親節(jié)點的類型,如果不一樣就直接替換

如果一樣

1.文本類型,直接替換文本值即可

2.元素類型,需要根據(jù)屬性來替換

這就證明了render方法里我們的oldProps的必要性,所以這里把新節(jié)點的真實dom賦值為舊節(jié)點的真實dom,先復(fù)用一波,待會再慢慢修改

updateProperties(newVnode, oldVNode.props)

export function patch(oldVNode, newVnode) {
    // //判斷類型是否一樣,不一樣直接用新虛擬節(jié)點替換老的
    if (oldVNode.type !== newVnode.type) {
        return oldVNode.domElement.parentNode.replaceChild(
            createDomElementFrom(newVnode), oldVNode.domElement
        )
    }
    // 類型相同,且是文本
    if (oldVNode.text) {
        return oldVNode.document.textContent = newVnode.text
    }
    // 類型一樣,不是文本,是標(biāo)簽,需要根據(jù)新節(jié)點的屬性更新老節(jié)點的屬性
    // 1. 復(fù)用老節(jié)點的真實dom
    let domElement = newVnode.domElement = oldVNode.domElement
    // 2. 根據(jù)最新的虛擬節(jié)點來更新屬性
    updateProperties(newVnode, oldVNode.props)
    // 比較兒子
    let oldChildren = oldVNode.children
    let newChildren = newVnode.children
    // 1. 老的有兒子,新的有兒子
    if (oldChildren.length > 0 && newChildren.length > 0) {
        // 對比兩個兒子(很復(fù)雜)
    } else if (oldChildren.length > 0) {
        // 2. 老的有兒子,新的沒兒子
        domElement.innerHTML = ''
    } else if (newChildren.length > 0) {
        // 3. 新增了兒子
        for (let i = 0; i < newChildren.length; i++) {
            // 把每個兒子加入元素里
            let ele = createDomElementFrom(newChildren[i])
            domElement.appendChild(ele)
        }
    }


}

2. diff算法

剛剛的渲染方法里,首先是對最外層元素進行對比,對于兒子節(jié)點,分為三種情況

1.老的有兒子,新的沒兒子(那么直接把真實節(jié)點的innerHTML設(shè)置為空即可)

2.老的沒兒子,新的有兒子(那么遍歷新的虛擬節(jié)點的兒子列表,把每一個都利用createElementFrom方法轉(zhuǎn)化為真實dom,append到最外層真實dom即可)

3.老的有兒子,新的有兒子,這個情況非常復(fù)雜,也就是我們要提及的diff算法

1. 對常見的dom做優(yōu)化

  • 前后追加元素
  • 正序和倒序元素
  • 中間插入元素

以最常見的ul列表為例子

舊的虛擬dom

let oldNode = h('div', {},
    h('li', { style: { background: 'red' }, key: 'A' }, 'A'),
    h('li', { style: { background: 'blue' }, key: 'B' }, 'A'),
    h('li', { style: { background: 'yellow' }, key: 'C' }, 'C'),
    h('li', { style: { background: 'green' }, key: 'D' }, 'D'),
);

情況1:末尾追加一個元素(頭和頭相同)

新的虛擬節(jié)點

在這里插入圖片描述

let newVnode = h('div', {},
    h('li', { style: { background: 'red' }, key: 'A' }, 'A'),
    h('li', { style: { background: 'blue' }, key: 'B' }, 'B'),
    h('li', { style: { background: 'yellow' }, key: 'C' }, 'C1'),
    h('li', { style: { background: 'green' }, key: 'D' }, 'D1'),
    h('li', { style: { background: 'black' }, key: 'D' }, 'E'),
);

eg:

// 比較是否同一個節(jié)點
function isSameVnode(oldVnode, newVnode) {
    return oldVnode.key == newVnode.key && oldVnode.type == newVnode.type
}
// diff
function updateChildren(parent, oldChildren, newChildren) {
    // 1. 創(chuàng)建舊節(jié)點開頭指針和結(jié)尾
    let oldStartIndex = 0
    let oldStartVnode = oldChildren[oldStartIndex];
    let oldEndIndex = oldChildren.length - 1
    let oldEndVnode = oldChildren[oldEndIndex];
    // 2. 創(chuàng)建新節(jié)點的指針
    let newStartIndex = 0
    let newStartVnode = newChildren[newStartIndex];
    let newEndIndex = newChildren.length - 1
    let newEndVnode = newChildren[newEndIndex];
    // 1. 當(dāng)從后面插入節(jié)點的時候,希望判斷老的孩子和新的孩子 循環(huán)的時候,誰先結(jié)束就停止循環(huán)
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        // 注意:比較對象是否相等,你不能用==,因為指向的位置可能不一樣,可以用type和key
        if (isSameVnode(oldStartVnode, newStartVnode)) {
            //patch比對更新
            patch(oldStartVnode, newStartVnode)
            // 移動指針
            oldStartVnode = oldChildren[++oldStartIndex]
            newStartVnode = newChildren[++newStartIndex]
        }
    }
    if (newStartIndex <= newEndIndex) {
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            parent.appendChild(createDomElementFrom(newChildren[i]))
        }
    }
}

在這里插入圖片描述

情況2:隊首添加一個節(jié)點(尾和尾)

在這里插入圖片描述

在這里插入圖片描述

頭和頭+尾和尾的處理方法:

我們通過parent.insertBefore(createDomElementFrom(newChildren[i]), beforeElement)使得末尾添加和頭部添加采用同一種處理方法

    // 如果是從前往后遍歷說明末尾新增了節(jié)點,會比原來的兒子后面新增了幾個
    // 也可以時從后往前遍歷,說明比原來的兒子前面新增了幾個
    if (newStartIndex <= newEndIndex) {
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            // 取得第一個值,null代表末尾
            let beforeElement = newChildren[newEndIndex + 1] == null ? null : newChildren[newEndIndex + 1].domElement   parent.insertBefore(createDomElementFrom(newChildren[i]), beforeElement)
        }
    }

圖解:

在這里插入圖片描述

MVVM=>數(shù)據(jù)一變,就調(diào)用patch

情況3:翻轉(zhuǎn)類型(頭和尾)

在這里插入圖片描述

尾和頭就不畫圖了

else if (isSameVnode(oldStartVnode, newEndVnode)) {
                // 頭和尾巴都不一樣,拿老的頭和新的尾巴比較
                patch(oldStartVnode, newEndVnode)
                // 把舊節(jié)點的頭部插入到舊節(jié)點末尾指針指向的節(jié)點之后一個
                parent.insertBefore(oldStartVnode.domElement, oldEndVnode.domElement.nextSibling)
                // 移動指針
                oldStartVnode = oldChildren[++oldStartIndex]
                newEndVnode = newChildren[--newEndIndex]
            } else if (isSameVnode(oldEndVnode, newStartVnode)) {
                // 頭和尾巴都不一樣,拿老的頭和新的尾巴比較
                patch(oldEndVnode, newStartVnode)
                // 把舊節(jié)點的頭部插入到舊節(jié)點末尾指針指向的節(jié)點之后一個
                parent.insertBefore(oldEndVnode.domElement, oldStartVnode.domElement)
                // 移動指針
                oldEndVnode = oldChildren[--oldEndIndex]
                newStartVnode = newChildren[++newStartIndex]
            } else {

情況4: 暴力比對復(fù)用

在這里插入圖片描述

else {
                // 都不一樣,就暴力比對
                // 需要先拿到新的節(jié)點去老的節(jié)點查找是否存在相同的key,存在則復(fù)用,不存在就創(chuàng)建插入即可
                // 1. 先把老的哈希
                let index = map[newStartVnode.key]//看看新節(jié)點的key在不在這個map里
                console.log(index);
                if (index == null) {//沒有相同的key
                    // 直接創(chuàng)建一個,插入到老的前面即可
                    parent.insertBefore(createDomElementFrom(newStartVnode),
                        oldStartVnode.domElement)
                } else {//有,可以復(fù)用
                    let toMoveNode = oldChildren[index]
                    patch(toMoveNode, newStartVnode)//復(fù)用要先patch一下
                    parent.insertBefore(toMoveNode.domElement, oldStartVnode.domElement)
                    oldChildren[index] = undefined
                    // 移動指正
                }
                newStartVnode = newChildren[++newStartIndex]
            }
// 寫一個方法,做成一個哈希表{a:0,b:1,c:2}
function createMapToIndex(oldChildren) {
    let map = {}
    for (let i = 0; i < oldChildren.length; i++) {
        let current = oldChildren[i]
        if (current.key) {
            map[current.key] = i
        }
    }
    return map
}

對于key的探討

1. 為什么不能沒有key

在這里插入圖片描述

2. 為什么key不能是index

在這里插入圖片描述

3. diff的遍歷方式

采用的是深度優(yōu)先,只會涉及到dom樹同層的比較,先對比父節(jié)點是否相同,然后對比兒子節(jié)點是否相同,相同的話對比孫子節(jié)點是否相同

在這里插入圖片描述

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!    

相關(guān)文章

  • vue3結(jié)合typescript中使用class封裝axios

    vue3結(jié)合typescript中使用class封裝axios

    這篇文章主要為大家介紹了vue3結(jié)合typescript中使用class封裝axios實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • 在vscode中統(tǒng)一vue編碼風(fēng)格的方法

    在vscode中統(tǒng)一vue編碼風(fēng)格的方法

    本篇文章主要介紹了在vscode中統(tǒng)一vue編碼風(fēng)格的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • vue router 路由跳轉(zhuǎn)方法講解

    vue router 路由跳轉(zhuǎn)方法講解

    這篇文章主要介紹了vue router 路由跳轉(zhuǎn)方法概述,使用到Vue的項目,我們最常見使用的就是Vue配套的Vue Router庫,本文結(jié)合示例代碼給大家詳細講解,需要的朋友可以參考下
    2022-12-12
  • Vue動態(tài)樣式綁定實例詳解

    Vue動態(tài)樣式綁定實例詳解

    眾所周知vue是操作dom元素的,那么如果有元素要動態(tài)綁定樣式,這種需求,還是要通過改變數(shù)據(jù)來改變視圖的樣式,下面這篇文章主要給大家介紹了關(guān)于Vue動態(tài)樣式綁定的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • vue異步加載dom元素之后無法獲得的解決

    vue異步加載dom元素之后無法獲得的解決

    這篇文章主要介紹了vue異步加載dom元素之后無法獲得的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • vue3快速實現(xiàn)主題切換功能的步驟詳解

    vue3快速實現(xiàn)主題切換功能的步驟詳解

    本文介紹一種基于css變量的主題切換實現(xiàn)方式,這種是最簡單,最直接,最容易理解的方式,實現(xiàn)的原理就是定義不同的HTML根標(biāo)簽元素的樣式,通過data屬性來區(qū)分不同主題css變量樣式,感興趣的朋友可以參考下
    2024-06-06
  • vue props傳值失敗 輸出undefined的解決方法

    vue props傳值失敗 輸出undefined的解決方法

    今天小編就為大家分享一篇vue props傳值失敗 輸出undefined的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • Vue 列表頁帶參數(shù)進詳情頁的操作(router-link)

    Vue 列表頁帶參數(shù)進詳情頁的操作(router-link)

    這篇文章主要介紹了Vue 列表頁帶參數(shù)進詳情頁的操作(router-link),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 詳解Vue的mixin策略

    詳解Vue的mixin策略

    這篇文章主要介紹了Vue的mixin策略的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)vue框架,感興趣的朋友可以了解下
    2020-11-11
  • axios異步提交表單數(shù)據(jù)的幾種方法

    axios異步提交表單數(shù)據(jù)的幾種方法

    這篇文章主要給大家介紹了關(guān)于axios異步提交表單數(shù)據(jù)的幾種方法,文中通過示例代碼介紹的非常詳細,對各位大家學(xué)習(xí)或者使用axios具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08

最新評論

无为县| 老河口市| 丹阳市| 深圳市| 名山县| 资阳市| 眉山市| 盐源县| 获嘉县| 古田县| 徐水县| 岐山县| 东源县| 阜阳市| 体育| 康马县| 陕西省| 甘孜县| 齐河县| 关岭| 宜州市| 凉城县| 科尔| 贡觉县| 辽阳县| 哈密市| 东源县| 石嘴山市| 阳新县| 南涧| 渝北区| 泗洪县| 北海市| 宁夏| 乐陵市| 平安县| 阿拉善左旗| 库尔勒市| 平乐县| 固原市| 天津市|