温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Vue编译优化的实现流程是什么

发布时间:2023-01-30 09:12:31 来源:亿速云 阅读:128 作者:iii 栏目:开发技术

本篇内容主要讲解“Vue编译优化的实现流程是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue编译优化的实现流程是什么”吧!

    动态节点收集与补丁标志

    1.传统diff算法的问题

    对于一个普通模板文件,如果只是标签中的内容发生了变化,那么最简单的更新方法很明显是直接替换标签中的文本内容。但是diff算法很明显做不到这一点,它会重新生成一棵虚拟DOM树,然后对两棵虚拟DOM树进行比较。很明显,与直接替换标签中的内容相比,传统diff算法需要做很多无意义的操作,如果能够去除这些无意义的操作,将会省下一笔很大的性能开销。其实,只要在模板编译时,标记出哪些节点是动态的,哪些是静态的,然后再通过虚拟DOM传递给渲染器,渲染器就能根据这些信息,直接修改对应节点,从而提高运行时性能。

    2.Block和PatchFlags

    对于一个传统的模板:

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

    在这个模板中,只用{{ bar }}是动态内容,因此在bar变量发生变化时,只需要修改p标签内的内容就行了。因此我们在这个模板对于的虚拟DOM中,加入patchFlag属性,以此来标签模板中的动态内容。

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

    对于不同的数值绑定,我们分别用不同的patch值来表示:

    • 数字1,代表节点有动态的textContent

    • 数字2,代表节点有动态的class绑定

    • 数字3,代表节点有动态的style绑定

    • 数字4,其他&hellip;

    我们可以新建一个枚举类型来表示这些值:

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

    这样我们就在虚拟DOM的创建阶段,将动态节点提取出来:

    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.收集动态节点

    首先我们创建收集动态节点的逻辑。

    const dynamicChildrenStack = []; // 动态节点栈
    let currentDynamicChildren = null; // 当前动态节点集合
    function openBlock() {
        // 创建一个新的动态节点栈
    	dynamicChildrenStack.push((currentDynamicChildren = []));
    }
    function closeBlock() {
        // openBlock创建的动态节点集合弹出
        currentDynamicChildren = dynamicChildrenStack.pop();
    }

    然后,我们在创建虚拟节点的时候,对动态节点进行收集。

    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) {
            // 如果有动态节点数组,直接更新动态节点数组
            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]);
        }
    }

    由于我们标记了不同的动态节点类型,因此我们可以针对性的完成靶向更新。

    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树

    组件的根节点必须作为Block角色,这样,从根节点开始的所有动态子代节点都会被收集到根节点的dynamicChildren数组中。除了根节点外,带有v-if、v-for这种结构化指令的节点,也会被作为Block角色,这些Block角色共同构成一棵Block树。

    静态提升

    假设有以下模板

    <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属性变化时,内容为静态文本的p标签节点也会跟着渲染一次,这很明显式不必要的。因此,我们可以使用“静态提升”,即将静态节点,提取到渲染函数之外,这样渲染函数在执行的时候,只是保持了对静态节点的引用,而不会重新创建虚拟节点。

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

    除了静态节点,对于静态props我们也可以将其进行静态提升处理。

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

    预字符化

    除了对节点进行静态提升外,我们还可以对于纯静态的模板进行预字符化。对于这样一个模板:

    <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
        ]));
    }

    这么做的优势:

    • 大块的静态内容可以通过innerHTML直接设置,在性能上具有一定优势

    • 减少创建虚拟节点带来的额外开销

    • 减少内存占用

    缓存内联事件处理函数

    当为组件添加内联事件时,每次新建一个组件,都会为该组件重新创建并绑定一个新的内联事件函数,为了避免这方面的无意义开销,我们可以对内联事件处理函数进行缓存。

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

    v-once

    v-once指令可以是组件只渲染一次,并且即使该组件绑定了动态参数,也不会更新。它与内联事件一样,也是使用了缓存,同时通过setBlockTracking(-1)阻止该VNode被Block收集。

    v-once的优点:

    • 避免组件更新时重新创建虚拟DOM带来的性能开销

    • 避免无用的Diff开销

    到此,相信大家对“Vue编译优化的实现流程是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    vue
    AI