溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue編譯優(yōu)化的實現流程是什么

發(fā)布時間:2023-01-30 09:12:31 來源:億速云 閱讀:126 作者:iii 欄目:開發(fā)技術

本篇內容主要講解“Vue編譯優(yōu)化的實現流程是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue編譯優(yōu)化的實現流程是什么”吧!

    動態(tài)節(jié)點收集與補丁標志

    1.傳統(tǒng)diff算法的問題

    對于一個普通模板文件,如果只是標簽中的內容發(fā)生了變化,那么最簡單的更新方法很明顯是直接替換標簽中的文本內容。但是diff算法很明顯做不到這一點,它會重新生成一棵虛擬DOM樹,然后對兩棵虛擬DOM樹進行比較。很明顯,與直接替換標簽中的內容相比,傳統(tǒng)diff算法需要做很多無意義的操作,如果能夠去除這些無意義的操作,將會省下一筆很大的性能開銷。其實,只要在模板編譯時,標記出哪些節(jié)點是動態(tài)的,哪些是靜態(tài)的,然后再通過虛擬DOM傳遞給渲染器,渲染器就能根據這些信息,直接修改對應節(jié)點,從而提高運行時性能。

    2.Block和PatchFlags

    對于一個傳統(tǒng)的模板:

    <div>
        <div>
            foo
        </div>
        <p>
            {{ bar }}
        </p>
    </div>

    在這個模板中,只用{{ bar }}是動態(tài)內容,因此在bar變量發(fā)生變化時,只需要修改p標簽內的內容就行了。因此我們在這個模板對于的虛擬DOM中,加入patchFlag屬性,以此來標簽模板中的動態(tài)內容。

    const vnode = {
        tag: 'div',
        children: [
            { tag: 'div', children: 'foo' },
            { tag: 'p', children: ctx.bar, patchFlag: 1 },
        ]
    }

    對于不同的數值綁定,我們分別用不同的patch值來表示:

    • 數字1,代表節(jié)點有動態(tài)的textContent

    • 數字2,代表節(jié)點有動態(tài)的class綁定

    • 數字3,代表節(jié)點有動態(tài)的style綁定

    • 數字4,其他&hellip;

    我們可以新建一個枚舉類型來表示這些值:

    enum PatchFlags {
        TEXT: 1,
        CLASS,
        STYLE,
        OTHER
    }

    這樣我們就在虛擬DOM的創(chuàng)建階段,將動態(tài)節(jié)點提取出來:

    const vnode = {
        tag: 'div',
        children: [
            { tag: 'div', children: 'foo' },
            { tag: 'p', children: ctx.bar, patchFlag: PatchFlags.TEXT },
        ],
        dynamicChildren: [
            { tag: 'p', children: ctx.bar, patchFlag: PatchFlags.TEXT },
        ]
    }

    3.收集動態(tài)節(jié)點

    首先我們創(chuàng)建收集動態(tài)節(jié)點的邏輯。

    const dynamicChildrenStack = []; // 動態(tài)節(jié)點棧
    let currentDynamicChildren = null; // 當前動態(tài)節(jié)點集合
    function openBlock() {
        // 創(chuàng)建一個新的動態(tài)節(jié)點棧
    	dynamicChildrenStack.push((currentDynamicChildren = []));
    }
    function closeBlock() {
        // openBlock創(chuàng)建的動態(tài)節(jié)點集合彈出
        currentDynamicChildren = dynamicChildrenStack.pop();
    }

    然后,我們在創(chuàng)建虛擬節(jié)點的時候,對動態(tài)節(jié)點進行收集。

    function createVNode(tag, props, children, flags) {
        const key = props && props.key;
        props && delete props.key;
        const vnode = {
            tag,
            props,
            children,
            key,
            patchFlags: flags
        }
        if(typeof flags !== 'undefined' && currentDynamicChildren) {
            currentDynamicChildren.push(vnode);
        }
        return vnode;
    }

    然后我們修改組件渲染函數的邏輯。

    render() {
        return (openBlock(), createBlock('div', null, [
            createVNode('p', { class: 'foo' }, null, 1),
            createVNode('p', { class: 'bar' }, null)
        ]));
    }
    function createBlock(tag, props, children) {
        const block = createVNode(tag, props, children);
        block.dynamicChildren = currentDynamicChildren;
        closeBlock();
        return block;
    }

    4.渲染器運行時支持

    function patchElement(n1, n2) {
        const el = n2.el = n1.el;
        const oldProps = n1.props;
        const newProps = n2.props;
        // ...
        if(n2.dynamicChildren) {
            // 如果有動態(tài)節(jié)點數組,直接更新動態(tài)節(jié)點數組
            patchBlockChildren(n1, n2);
        } else {
            patchChildren(n1, n2, el);
        }
    }
    function pathcBlockChildren(n1, n2) {
        for(let i = 0; i < n2.dynamicChildren.length; i++) {
            patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i]);
        }
    }

    由于我們標記了不同的動態(tài)節(jié)點類型,因此我們可以針對性的完成靶向更新。

    function patchElement(n1, n2) {
        const el = n2.el = n1.el;
        const oldProps = n1.props;
        const newProps = n2.props;
        if(n2.patchFlags) {
            if(n2.patchFlags === 1) {
                // 只更新內容
            } else if(n2.patchFlags === 2) {
                // 只更新class
            } else if(n2.patchFlags === 3) {
                // 只更新style
            } else {
                // 更新所有
                for(const k in newProps) {
                    if(newProps[key] !== oldProps[key]) {
                    	patchProps(el, key, oldProps[k], newProps[k]);
                    }
                }
                for(const k in oldProps) {
                    if(!key in newProps) {
                        patchProps(el, key, oldProps[k], null);
                    }
                }
            }
        }
        patchChildren(n1, n2, el);
    }

    5.Block樹

    組件的根節(jié)點必須作為Block角色,這樣,從根節(jié)點開始的所有動態(tài)子代節(jié)點都會被收集到根節(jié)點的dynamicChildren數組中。除了根節(jié)點外,帶有v-if、v-for這種結構化指令的節(jié)點,也會被作為Block角色,這些Block角色共同構成一棵Block樹。

    靜態(tài)提升

    假設有以下模板

    <div>
        <p>
            static text
        </p>
        <p>
            {{ title }}
        </p>
    </div>

    默認情況下,對應的渲染函數為:

    function render() {
        return (openBlock(), createBlock('div', null, [
            createVNode('p', null, 'static text'),
            createVNode('p', null, ctx.title, 1 /* TEXT */)
        ]))
    }

    在這段代碼中,當ctx.title屬性變化時,內容為靜態(tài)文本的p標簽節(jié)點也會跟著渲染一次,這很明顯式不必要的。因此,我們可以使用“靜態(tài)提升”,即將靜態(tài)節(jié)點,提取到渲染函數之外,這樣渲染函數在執(zhí)行的時候,只是保持了對靜態(tài)節(jié)點的引用,而不會重新創(chuàng)建虛擬節(jié)點。

    const hoist1 = createVNode('p', null, 'static text');
    function render() {
        return (openBlock(), createBlock('div', null, [
            hoist1,
            createVNode('p', null, ctx.title, 1 /* TEXT */)
        ]))
    }

    除了靜態(tài)節(jié)點,對于靜態(tài)props我們也可以將其進行靜態(tài)提升處理。

    const hoistProps = { foo: 'bar', a: '1' };
    function render() {
        return (openBlock(), createBlock('div', null, [
            hoist1,
            createVNode('p', hoistProps, ctx.title, 1 /* TEXT */)
        ]))
    }

    預字符化

    除了對節(jié)點進行靜態(tài)提升外,我們還可以對于純靜態(tài)的模板進行預字符化。對于這樣一個模板:

    <templete>
    	<p></p>
        <p></p>
        <p></p>
        <p></p>
        <p></p>
        ...
        <p></p>
        <p></p>
        <p></p>
        <p></p>
    </templete>

    我們完全可以將其預處理為:

    const hoistStatic = createStaticVNode('<p></p><p></p><p></p><p></p>...<p></p><p></p><p></p><p></p>');
    render() {
        return (openBlock(), createBlock('div', null, [
    		hoistStatic
        ]));
    }

    這么做的優(yōu)勢:

    • 大塊的靜態(tài)內容可以通過innerHTML直接設置,在性能上具有一定優(yōu)勢

    • 減少創(chuàng)建虛擬節(jié)點帶來的額外開銷

    • 減少內存占用

    緩存內聯事件處理函數

    當為組件添加內聯事件時,每次新建一個組件,都會為該組件重新創(chuàng)建并綁定一個新的內聯事件函數,為了避免這方面的無意義開銷,我們可以對內聯事件處理函數進行緩存。

    function render(ctx, cache) {
        return h(Comp, {
            onChange: cache[0] || cache[0] = ($event) => (ctx.a + ctx.b);
        })
    }

    v-once

    v-once指令可以是組件只渲染一次,并且即使該組件綁定了動態(tài)參數,也不會更新。它與內聯事件一樣,也是使用了緩存,同時通過setBlockTracking(-1)阻止該VNode被Block收集。

    v-once的優(yōu)點:

    • 避免組件更新時重新創(chuàng)建虛擬DOM帶來的性能開銷

    • 避免無用的Diff開銷

    到此,相信大家對“Vue編譯優(yōu)化的實現流程是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    vue
    AI