Vue编译优化实现流程详解

 更新时间:2023年1月30日 08:20  点击:809 作者:volit_

动态节点收集与补丁标志

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,其他…

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

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编译优化实现流程详解的文章就介绍到这了,更多相关Vue编译优化内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/weixin_49971653/article/details/128757

[!--infotagslink--]

相关文章

  • vue中activated的用法

    这篇文章主要介绍了vue中activated的用法,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2021-01-03
  • 基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能

    这篇文章主要介绍了基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-23
  • Antd-vue Table组件添加Click事件,实现点击某行数据教程

    这篇文章主要介绍了Antd-vue Table组件添加Click事件,实现点击某行数据教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-17
  • vue 实现动态路由的方法

    这篇文章主要介绍了vue 实现动态路由的方法,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-06
  • vue 监听 Treeselect 选择项的改变操作

    这篇文章主要介绍了vue 监听 Treeselect 选择项的改变操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
  • Vue基于localStorage存储信息代码实例

    这篇文章主要介绍了Vue基于localStorage存储信息代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-16
  • Vue组件跨层级获取组件操作

    这篇文章主要介绍了Vue组件跨层级获取组件操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-28
  • vue 获取到数据但却渲染不到页面上的解决方法

    这篇文章主要介绍了vue 获取到数据但却渲染不到页面上的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-19
  • antdesign-vue结合sortablejs实现两个table相互拖拽排序功能

    这篇文章主要介绍了antdesign-vue结合sortablejs实现两个table相互拖拽排序功能,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-09
  • vuejs element table 表格添加行,修改,单独删除行,批量删除行操作

    这篇文章主要介绍了vuejs element table 表格添加行,修改,单独删除行,批量删除行操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-18
  • vue项目多环境配置(.env)的实现

    最常见的多环境配置,就是开发环境配置,和生产环境配置,本文主要介绍了vue项目多环境配置的实现,感兴趣的可以了解一下...2021-07-20
  • vue项目页面嵌入代码块vue-prism-editor的实现

    这篇文章主要介绍了vue项目页面嵌入代码块vue-prism-editor的实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-30
  • Vue中slot-scope的深入理解(适合初学者)

    这篇文章主要给大家介绍了关于Vue中slot-scope的深入理解,这个教程非常适合初学者,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-17
  • vue treeselect获取当前选中项的label实例

    这篇文章主要介绍了vue treeselect获取当前选中项的label实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
  • Vue 3.0中jsx语法的使用

    这篇文章主要介绍了Vue 3.0 中 jsx 语法使用,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2020-11-13
  • 解决vue的router组件component在import时不能使用变量问题

    这篇文章主要介绍了解决vue的router组件component在import时不能使用变量问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
  • vue实现同时设置多个倒计时

    这篇文章主要为大家详细介绍了vue实现同时设置多个倒计时,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-05-20
  • vue Treeselect下拉树只能选择第N级元素实现代码

    这篇文章主要介绍了vue Treeselect下拉树只能选择第N级元素实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
  • vue实现div单选多选功能

    这篇文章主要为大家详细介绍了vue实现div单选多选功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-07-16
  • Ant design vue table 单击行选中 勾选checkbox教程

    这篇文章主要介绍了Ant design vue table 单击行选中 勾选checkbox教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-25