欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Vue3的虛擬DOM是如何生成的

 更新時(shí)間:2023年09月11日 09:10:38   作者:田八  
這篇文章給大家詳細(xì)介紹了 Vue3 的虛擬DOM生成規(guī)則,文章通過(guò)代碼示例和圖片介紹的非常詳細(xì),具有一定的參考價(jià)值,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

h 函數(shù)

在官網(wǎng)上可以看到對(duì)h函數(shù)的介紹和函數(shù)簽名;

可以先去看看官網(wǎng)的介紹,然后再來(lái)看看源碼的實(shí)現(xiàn),傳送門:

h 函數(shù)的實(shí)現(xiàn)

還是跟著我們之前的節(jié)奏,可以直接在h函數(shù)調(diào)用上面打上斷點(diǎn),然后開(kāi)始調(diào)試進(jìn)入源碼:

const {h} = Vue;
debugger;
h('div');

直接就這樣進(jìn)入了h函數(shù)的實(shí)現(xiàn),我們來(lái)看看h函數(shù)的實(shí)現(xiàn):

function h(type, propsOrChildren, children) {
    // 通過(guò)參數(shù)數(shù)量來(lái)進(jìn)行重載
    const l = arguments.length;
    // 如果參數(shù)數(shù)量為2,那么就有兩種情況
    if (l === 2) {
        // 如果第二個(gè)參數(shù)是對(duì)象,并且不是數(shù)組
        if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
            // 如果第二個(gè)參數(shù)是虛擬dom,那么就將第二個(gè)參數(shù)作為子節(jié)點(diǎn)進(jìn)行處理
            if (isVNode(propsOrChildren)) {
                return createVNode(type, null, [propsOrChildren]);
            }
            // 如果第二個(gè)參數(shù)是對(duì)象,那么就將第二個(gè)參數(shù)作為props進(jìn)行處理
            return createVNode(type, propsOrChildren);
        } else {
            // 如果第二個(gè)參數(shù)是數(shù)組,那么就將第二個(gè)參數(shù)作為子節(jié)點(diǎn)進(jìn)行處理
            return createVNode(type, null, propsOrChildren);
        }
    } else {
        // 如果參數(shù)數(shù)量不是2
        if (l > 3) {
            // 并且參數(shù)數(shù)量大于3,那么就將第三個(gè)參數(shù)以及后面的參數(shù)作為子節(jié)點(diǎn)進(jìn)行處理
            children = Array.prototype.slice.call(arguments, 2);
        } else if (l === 3 && isVNode(children)) {
            // 如果參數(shù)數(shù)量等于3,并且第三個(gè)參數(shù)是虛擬dom,那么就將第三個(gè)參數(shù)作為子節(jié)點(diǎn)進(jìn)行處理
            children = [children];
        }
        // 最后將第二個(gè)參數(shù)作為props,其余的參數(shù)作為子節(jié)點(diǎn)進(jìn)行處理
        return createVNode(type, propsOrChildren, children);
    }
}

h函數(shù)就是一個(gè)重載函數(shù),根據(jù)參數(shù)的不同,會(huì)有不同的處理邏輯,其實(shí)沒(méi)有什么好看的;

它最后將所有的參數(shù)都傳遞給了createVNode函數(shù),也就是核心是createVNode函數(shù);

createVNode 函數(shù)

由于我們上面的示例代碼中,只傳入了一個(gè)參數(shù),所以會(huì)跳過(guò)很多邏輯,簡(jiǎn)化后的createVNode函數(shù)如下:

function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
    // 獲取 shapeFlag
    const shapeFlag = isString(type) ? 1 :
        isSuspense(type) ? 128 :
            isTeleport(type) ? 64 :
                isObject(type) ? 4 :
                    isFunction(type) ? 2 : 0;
    // 如果是一個(gè)組件,并且還被設(shè)置成響應(yīng)式的了,則會(huì)提示并解包
    if (shapeFlag & 4 && isProxy(type)) {
        type = toRaw(type);
        warn(
            `Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with \`markRaw\` or using \`shallowRef\` instead of \`ref\`.`,
            `
Component that was made reactive: `,
            type
        );
    }
    // 最后調(diào)用 createBaseVNode 創(chuàng)建 VNode
    return createBaseVNode(
        type,
        props,
        children,
        patchFlag,
        dynamicProps,
        shapeFlag,
        isBlockNode,
        true
    );
}

這里主要是獲取了shapeFlag,我們上面?zhèn)魅肓艘粋€(gè)字符串的div,所以shapeFlag的值為1;

這里的shapeFlag其實(shí)是一個(gè)二進(jìn)制的值,它的值是由type的類型來(lái)決定的,在ts的源碼中有他們的定義:

// packages\shared\src\shapeFlags.ts
export const enum ShapeFlags {
    ELEMENT = 1, // 普通dom元素  二進(jìn)制:0000 0001  十進(jìn)制:1
    FUNCTIONAL_COMPONENT = 1 << 1, // 函數(shù)組件  二進(jìn)制:0000 0010  十進(jìn)制:2
    STATEFUL_COMPONENT = 1 << 2, // 有狀態(tài)組件  二進(jìn)制:0000 0100  十進(jìn)制:4
    TEXT_CHILDREN = 1 << 3, // 文本子節(jié)點(diǎn)  二進(jìn)制:0000 1000  十進(jìn)制:8
    ARRAY_CHILDREN = 1 << 4, // 數(shù)組子節(jié)點(diǎn)  二進(jìn)制:0001 0000  十進(jìn)制:16
    SLOTS_CHILDREN = 1 << 5, // 插槽  二進(jìn)制:0010 0000  十進(jìn)制:32
    TELEPORT = 1 << 6, // TELEPORT組件  二進(jìn)制:0100 0000  十進(jìn)制:64
    SUSPENSE = 1 << 7, // SUSPENSE組件  二進(jìn)制:1000 0000  十進(jìn)制:128
    COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 沒(méi)弄清  二進(jìn)制:0001 0000 0000  十進(jìn)制:256
    COMPONENT_KEPT_ALIVE = 1 << 9, // 沒(méi)弄清  二進(jìn)制:0010 0000 0000  十進(jìn)制:512
    COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 普通組件,應(yīng)該是有狀態(tài)組件和函數(shù)組件的并集
}

這里我們可以驗(yàn)證一下這些值,寫(xiě)個(gè)demo來(lái)看看:

    const {h} = Vue;
// 普通元素
const element = h('div');
console.log('ELEMENT', element.shapeFlag);
// 函數(shù)式組件
const functionalComponent = h(() => h('div'));
console.log('FUNCTIONAL_COMPONENT', functionalComponent.shapeFlag);
// 有狀態(tài)組件
const statefulComponent = h({
    render() {
        return h('div');
    }
});
console.log('STATEFUL_COMPONENT', statefulComponent.shapeFlag);
// 文本子節(jié)點(diǎn)
const textChildren = h('div', 'text');
console.log('TEXT_CHILDREN', textChildren.shapeFlag);
// 數(shù)組子節(jié)點(diǎn)
const arrayChildren = h('div', [h('span'), h('span')]);
console.log('ARRAY_CHILDREN', arrayChildren.shapeFlag);
// 插槽子節(jié)點(diǎn)
const slotsChildren = h({
    render() {
        return h('div', this.$slots.default());
    }
}, null, () => 'slotChildren');
console.log('SLOTS_CHILDREN', slotsChildren.shapeFlag);
// teleport組件
const teleport = h(Vue.Teleport);
console.log('TELEPORT', teleport.shapeFlag);
// suspense組件
const suspense = h(Vue.Suspense);
console.log('SUSPENSE', suspense.shapeFlag);

可以看到的是驗(yàn)證結(jié)果和我們上面的定義是一致的:

這里的文本子節(jié)點(diǎn)和數(shù)組子節(jié)點(diǎn)的值是917,這里的值是由shapeFlag的值和TEXT_CHILDRENARRAY_CHILDREN的值進(jìn)行或運(yùn)算得到,這就要進(jìn)入到createBaseVNode函數(shù)中去看看了。

createBaseVNode 函數(shù)

這里的createBaseVNode函數(shù)就是定義了VNode的一些屬性,我們拿文本子節(jié)點(diǎn)來(lái)做示例看看運(yùn)行邏輯(刪除不會(huì)執(zhí)行的邏輯的簡(jiǎn)化版代碼):

function createBaseVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, shapeFlag = type === Fragment ? 0 : 1, isBlockNode = false, needFullChildrenNormalization = false) {
    // 定義 vnode
    const vnode = {
        __v_isVNode: true,
        __v_skip: true,
        type,
        props,
        key: props && normalizeKey(props),
        ref: props && normalizeRef(props),
        scopeId: currentScopeId,
        slotScopeIds: null,
        children,
        component: null,
        suspense: null,
        ssContent: null,
        ssFallback: null,
        dirs: null,
        transition: null,
        el: null,
        anchor: null,
        target: null,
        targetAnchor: null,
        staticCount: 0,
        shapeFlag,
        patchFlag,
        dynamicProps,
        dynamicChildren: null,
        appContext: null,
        ctx: currentRenderingInstance
    };
    // 普通節(jié)點(diǎn)固定走這個(gè)分支
    if (needFullChildrenNormalization) {
        // 使用 normalizeChildren 處理 children
        normalizeChildren(vnode, children);
    }
    // 最后返回 vnode
    return vnode;
}

這里的代碼并不復(fù)雜,就是定義了vnode,然后對(duì)children進(jìn)行了處理,最后返回了vnode;

我們當(dāng)前測(cè)試的文本子節(jié)點(diǎn),shapeFlag的值為9,這里就是通過(guò)normalizeChildren函數(shù)來(lái)處理的,我們來(lái)看看normalizeChildren函數(shù)的實(shí)現(xiàn):

function normalizeChildren(vnode, children) {
    let type = 0;
    const { shapeFlag } = vnode;
    if (children == null) {
        // ...
    } else if (isArray(children)) {
        // ...
    } else if (typeof children === "object") {
        // ...
    } else if (isFunction(children)) {
        // ...
    } else {
        // 走到這里,說(shuō)明 children 需要被規(guī)范為文本節(jié)點(diǎn)
        // 直接轉(zhuǎn)為字符串
        children = String(children);
        // 如果是 teleport ,子節(jié)點(diǎn)會(huì)被標(biāo)記為 16,也就是數(shù)組節(jié)點(diǎn)
        if (shapeFlag & 64) {
            type = 16;
            // 這里會(huì)將 children 轉(zhuǎn)為數(shù)組
            children = [createTextVNode(children)];
        } else {
            // 如果是普通節(jié)點(diǎn),直接標(biāo)記為文本節(jié)點(diǎn),也就是 8
            type = 8;
        }
    }
    // 最后將 children 賦值給 vnode.children
    vnode.children = children;
    // 然后將 type 的值進(jìn)行或運(yùn)算,賦值給 vnode.shapeFlag
    vnode.shapeFlag |= type;
}

可以看到這里寫(xiě)了一堆條件分支,來(lái)判斷不同的子節(jié)點(diǎn)類型,最后將children賦值給vnode.children,然后將type的值進(jìn)行或運(yùn)算,賦值給vnode.shapeFlag;

或運(yùn)算會(huì)得到什么結(jié)果呢?其實(shí)我們完全可以自己嘗試一下:

1 | 8 的結(jié)果是9,這里的1就是ELEMENT8就是TEXT_CHILDREN,所以最后的結(jié)果就是ELEMENT | TEXT_CHILDREN,也就是9;

位運(yùn)算

這樣做有什么意義呢?其實(shí)閱讀了這么長(zhǎng)時(shí)間的源碼,不難發(fā)現(xiàn)經(jīng)常會(huì)出現(xiàn)這樣的代碼:

if (shapeFlag & 8) {
    // ...
}

這里就是一個(gè)位運(yùn)算,這樣寫(xiě)無(wú)疑是增加了閱讀的難度,但是對(duì)代碼的性能以及一些邏輯上的判斷是有幫助的;

還是我們剛才的例子,我們來(lái)看看ELEMENTTEXT_CHILDREN合并的值是9,ELEMENTARRAY_CHILDREN合并的值是17;

我們對(duì)它進(jìn)行一個(gè)位運(yùn)算,看看結(jié)果是什么:

  • ELEMENTTEXT_CHILDREN合并的值,與所以類型進(jìn)行與運(yùn)算,結(jié)果如下:

  • ELEMENTARRAY_CHILDREN合并的值,與所有類型進(jìn)行與運(yùn)算,結(jié)果如下:

可以看到合并后的值,只會(huì)與參與合并的值進(jìn)行與運(yùn)算得到的結(jié)果是參與合并的值,這樣就可以通過(guò)與運(yùn)算來(lái)判斷shapeFlag的值是否包含某個(gè)類型;

而將這個(gè)過(guò)程進(jìn)行二進(jìn)制來(lái)描述,就是這樣的:

# 這是 ELEMENT 和 TEXT_CHILDREN 合并的值
0000 1001
# 這是 ELEMENT 的值
0000 0001
# 進(jìn)行與運(yùn)算
0000 1001
&&&& &&&&
0000 0001
= = = = =
0000 0001

通過(guò)上面的例子,其實(shí)與運(yùn)算就是將兩個(gè)值的二進(jìn)制中的相同位置的值進(jìn)行比較,如果都是1,那么結(jié)果就是1,否則就是0;

Vue將每個(gè)節(jié)點(diǎn)的類型都定義成了2的n次方,這樣就可以避免會(huì)出現(xiàn)相同位置的1,這樣在進(jìn)行或運(yùn)算的時(shí)候,就可以將所有的類型進(jìn)行合并,從而產(chǎn)生一個(gè)新的值;

如果是相同類型的節(jié)點(diǎn),那么shapeFlag的值就是相同的,在進(jìn)行或運(yùn)算的時(shí)候會(huì)得到相同的值,新值和原來(lái)的值是相同的,因?yàn)楸旧砭桶诉@個(gè)類型;

這樣新值就會(huì)包含所有參與合并的值的類型,就可以通過(guò)與運(yùn)算來(lái)判斷shapeFlag的值是否包含某個(gè)類型,設(shè)計(jì)非常的巧妙;

總結(jié)

這一篇主要學(xué)習(xí)了vnode的擦創(chuàng)建過(guò)程,其實(shí)一個(gè)vnode就是一個(gè)js對(duì)象,本身并沒(méi)有什么特殊的;

特殊的是這個(gè)vnode自帶的屬性,例如這一章詳細(xì)介紹的sahpeFlag,這個(gè)屬性就是通過(guò)位運(yùn)算來(lái)進(jìn)行合并的,這樣就可以通過(guò)與運(yùn)算來(lái)判斷shapeFlag的值是否包含某個(gè)類型;

而一個(gè)vnode中并不是只有一個(gè)shapeFlag屬性,還有很多其他的屬性,例如我們傳入的props、childrenslot等等;

這些屬性在Vue的整個(gè)系統(tǒng)中又是如何使用的呢?這些將會(huì)在我們繼續(xù)深入源碼的過(guò)程中一一揭曉;

以上就是詳解Vue3的虛擬DOM是如何生成的的詳細(xì)內(nèi)容,更多關(guān)于Vue3虛擬DOM生成的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • VueRouter?原理解讀之初始化流程

    VueRouter?原理解讀之初始化流程

    這篇文章主要為大家介紹了VueRouter原理解讀之初始化流程實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • IDEA安裝vue插件圖文詳解

    IDEA安裝vue插件圖文詳解

    這篇文章主要為大家詳細(xì)介紹了IDEA安裝vue插件圖文,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • vue移動(dòng)端html5頁(yè)面根據(jù)屏幕適配的四種解決方法

    vue移動(dòng)端html5頁(yè)面根據(jù)屏幕適配的四種解決方法

    在vue移動(dòng)端h5頁(yè)面當(dāng)中,其中適配是經(jīng)常會(huì)遇到的問(wèn)題,這塊主要有四個(gè)方法可以適用。這篇文章主要介紹了vue移動(dòng)端h5頁(yè)面根據(jù)屏幕適配的四種方案 ,需要的朋友可以參考下
    2018-10-10
  • 使用vue實(shí)現(xiàn)一個(gè)電子簽名組件的示例代碼

    使用vue實(shí)現(xiàn)一個(gè)電子簽名組件的示例代碼

    這篇文章主要介紹了使用vue實(shí)現(xiàn)一個(gè)電子簽名組件的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • uniapp使用scroll-view下拉刷新無(wú)法取消的坑及解決

    uniapp使用scroll-view下拉刷新無(wú)法取消的坑及解決

    這篇文章主要介紹了uniapp使用scroll-view下拉刷新無(wú)法取消的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • vue實(shí)現(xiàn)商品規(guī)格選擇功能

    vue實(shí)現(xiàn)商品規(guī)格選擇功能

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)商品規(guī)格選擇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue中使用swiper5方式

    vue中使用swiper5方式

    這篇文章主要介紹了vue中使用swiper5方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • vue項(xiàng)目實(shí)現(xiàn)面包屑導(dǎo)航

    vue項(xiàng)目實(shí)現(xiàn)面包屑導(dǎo)航

    這篇文章主要為大家詳細(xì)介紹了vue項(xiàng)目實(shí)現(xiàn)面包屑導(dǎo)航,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue數(shù)據(jù)雙向綁定的注意點(diǎn)

    vue數(shù)據(jù)雙向綁定的注意點(diǎn)

    這篇文章主要為大家詳細(xì)介紹了vue數(shù)據(jù)雙向綁定的注意點(diǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Vue彈窗Dialog最佳使用方案實(shí)戰(zhàn)

    Vue彈窗Dialog最佳使用方案實(shí)戰(zhàn)

    這篇文章主要為大家介紹了極度舒適的Vue彈窗Dialog最佳使用方案實(shí)戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11

最新評(píng)論