关于前端:🚀🚀🚀小白都能看懂的Vue渲染过程4000字建议收藏🔥🔥🔥

前言

作者简介:Quixn,专一于 Node.js 技术栈分享,前端从 JavaScript 到 Node.js,再到后端数据库,优质文章举荐,【小Q全栈指南】作者,Github 博客开源我的项目 github.com/Quixn…

大家好,我是Quixn。明天简略聊聊 Vue 的整个渲染渲染过程。嫌文章太长者,可间接到文末看精简版总结哦。

Vue 的整个渲染过程

Vue的渲染引擎是基于Virtual DOM实现的。上面是Vue的整个渲染过程的简要概述:

1、解析模板:Vue首先会解析模板,并生成一个形象语法树(AST)。

2、生成渲染函数:Vue依据AST生成一个渲染函数,该函数用于生成Virtual DOM树。

3、执行渲染函数:当组件的状态发生变化时,Vue会从新执行渲染函数,生成一个新的Virtual DOM树。

4、比照新旧Virtual DOM树:Vue会比照新旧Virtual DOM树的差别,找出须要更新的局部。

5、更新DOM:Vue会依据差别更新实在的DOM树。

如果你是Vue的初学者,以下提供一份简略的流程图和一则小故事不便你记忆和了解,也可间接跳过哦。

小故事:

当你应用Vue开发利用时,就好比是你成为了一名餐厅的厨师,你须要将菜单(Vue的模板)转化为美味的佳肴(渲染出的页面)。

首先,你须要将菜单交给服务员(Vue编译器)来翻译,而后把翻译后的菜单(AST)给到你。翻译实现后,你须要依据这份菜单开始烹制食材(Vue的渲染函数)。

你应用菜单上的指令和属性来抉择要应用的食材(渲染函数中的数据)和烹饪办法(Vue的指令)。这些指令和属性通知你哪些食材须要加工,哪些须要展现给客人,哪些不须要展现等等。

接着,你开始烹饪。你遵循菜单上的步骤来解决食材(执行渲染函数),最终你烹制出了一份美味佳肴(生成了Virtual DOM树)。

最初,你须要将这份美味佳肴上菜(渲染到页面上),让客人能够品味到你的作品(浏览器展现渲染后果)。如果客人有一些不喜爱的菜品(须要更新的局部),你须要回到厨房再烹制一份,而后再把新菜品端给客人(更新Virtual DOM树和从新渲染页面)。

这就是Vue的整个渲染过程,将模板转化为AST,而后生成渲染函数,最初渲染到页面上,一次又一次地循环,直到呈现出残缺的页面。

Vue的渲染过程中,最要害的局部是生成Virtual DOM树比照新旧Virtual DOM树的差别。Vue通过一种叫做Virtual DOM Diff算法的算法来实现这一过程。该算法会比照新旧Virtual DOM树的构造和属性,找出差别,并将差别利用到实在的DOM树上,从而实现高效的更新。

1、解析模板

Vue利用启动时,会将模板传入Vue的编译器进行解析和编译Vue的编译器将模板解析成一个形象语法树(AST),并将AST转换为渲染函数

上面是Vue模板解析的根本流程:

首先,Vue会将模板字符串转换为AST树的模式。在这个过程中,Vue应用了一个叫做htmlparser2的库来解析模板。

Vue将模板中的指令属性转换为对应的AST节点。这些节点被称为指令节点或属性节点。指令节点蕴含指令名称表达式,属性节点蕴含属性名称和属性值。

在模板解析的过程中,Vue会将模板中的文本内容转换为文本节点文本节点中蕴含文本内容

2、生成渲染函数

解析完模板后,Vue会对AST进行动态剖析(至于这个过程是如何进行动态剖析的,前面会专门出一篇文章来讲,此处不做剖析),以便优化渲染函数的生成。动态剖析能够检测到不须要更新的节点,从而防止不必要的更新。

最初,VueAST转换为渲染函数渲染函数是一个JavaScript函数,用于生成Virtual DOM树并将其渲染到实在的DOM树上。

上面是一个简略的Vue模板和生成的AST节点的示例:

<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <p v-if="showText">{{ text }}</p>
  </div>
</template>

解析后的生成的AST节点:

{
  type: 1,
  tag: 'div',
  attrsList: [{ name: 'class', value: 'container' }],
  attrsMap: { class: 'container' },
  children: [
    {
      type: 1,
      tag: 'h1',
      children: [
        {
          type: 2,
          expression: '_s(title)',
          text: '{{ title }}'
        }
      ]
    },
    {
      type: 1,
      tag: 'p',
      if: 'showText',
      ifConditions: [
        {
          exp: 'showText',
          block: {
            type: 1,
            tag: 'p',
            children: [
              {
                type: 2,
                expression: '_s(text)',
                text: '{{ text }}'
              }
            ]
          }
        }
      ]
    }
  ]
}

在这个示例中,解析后的AST树中蕴含一个div节点两个子节点,一个h1节点和一个p节点。h1节点中蕴含一个文本节点,它的文本内容是模板中的{{ title }} p节点有一个v-if指令,Vue会将这个指令转换为一个if属性,并在ifConditions数组中保留条件对应的节点。在理论的渲染过程中,Vue会依据if属性的值来判断是否须要渲染这个节点。

下面咱们曾经将模板转换为AST树了,接下来就是将AST树转换生成渲染函数。在 Vue 中,将 AST 转换为渲染函数的过程,实际上是在编译阶段实现的。编译器将模板转换为一个蕴含渲染函数的函数,这个函数接管一个参数 render,执行后会返回一个 VNode 节点。

具体来说,编译器将模板解析为 AST,而后对 AST 进行动态剖析,进行一系列的优化,最初通过遍历 AST 生成渲染函数。生成渲染函数的过程中,会用到源码里的 codegen.js 文件中定义的一些函数来生成代码。

上面是一个简略的示例,假如咱们有这样一个 Vue 模板:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="handleClick">Click Me</button>
  </div>
</template>

编译器将会把这个模板解析为 AST,而后遍历 AST 生成一个渲染函数:

function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */),
    _createVNode("button", { onClick: _cache[1] || (_cache[1] = $event => _ctx.handleClick($event)) }, "Click Me")
  ]))
}

下面的渲染函数中,_ctx 是组件实例的上下文对象,_cache 是一个缓存对象,存储了事件处理函数等。

渲染函数返回的是一个 VNode 节点,示意要渲染的内容。在渲染过程中,Vue 会依据这个 VNode 节点构建实在的 DOM 节点并插入到页面中。

3、执行渲染函数

通过下面两个步骤咱们曾经将模板解析并生成了渲染函数了,接下来Vue又是如何执行渲染函数的呢?

Vue 中,执行渲染函数的过程是通过调用虚构 DOMpatch 办法来实现的。patch 办法接管两个参数,一个是旧 VNode,另一个是新 VNode。它会比拟这两个 VNode 的差别,并将差别利用到实在的 DOM 上。

在组件初始化或数据更新时,Vue 会生成新的 VNode 并与旧的 VNode 进行比拟,找出两者之间的差别,并将这些差别利用到实在的 DOM 上。

上面是一个简略的示例,它演示了如何应用渲染函数来创立一个蕴含一个按钮和一个计数器的组件:

<!-- App.vue -->
<template>
  <div>
    <button @click="increment">{{ count }}</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  render() {
    return h('div', [
      h('button', { on: { click: this.increment } }, this.count)
    ])
  }
}
</script>

在下面的代码中,咱们应用render函数来创立 VNode 树,并返回一个根节点。在这个例子中, 函数render里的h函数是 Vue 提供的用于创立 VNode 的辅助函数。

在这个例子中,如果用户点击了按钮,Vue 将会从新调用render函数,并返回一个更新后的 VNode 树。而后,Vue 将会将这个 VNode 树与之前的 VNode 树进行比拟,并计算出须要对 DOM 进行的更改。这些更改将被传递给虚构 DOM patch 办法,该办法将这些更改利用到实在的 DOM 上,从而实现更新视图的成果。

这里只是一个简略的示例,实际上,渲染函数可能更加简单,能够应用计算属性、循环等性能来实现更加弱小的动静 UI。然而无论如何,渲染函数的基本原理都是通过比拟新旧 VNode 的差别,并将这些差别利用到实在的 DOM 上来实现更新视图的成果。

这个示例的过程就像咱们在玩扑克牌时,每个玩家手中领有一些牌,须要一直地调整牌的地位,直到失去最优的牌型。

假如咱们有两个玩家AB,他们各自手中有5张牌,每次玩家A会换掉一张手牌,而后将手牌展现给所有玩家看。其余玩家能够察看A的换牌过程,而后依据A的牌型和其余玩家手中的牌来决定本人是否换牌。

在这个过程中,A换掉牌相当于更新了数据,其余玩家察看A换牌的过程相当于生成新的虚构DOM树,而后依据新旧虚构DOM树的变动来决定是否更新视图。每个玩家都能够通过观察A换牌的过程来失去最新的牌型和最优的决策。

类比到Vue中,每个组件都有本人的数据,当数据发生变化时,Vue会依据渲染函数生成新的虚构DOM树,而后通过比对新旧虚构DOM树来判断哪些节点须要更新。最终,Vue会将更新的节点通过DOM操作更新到视图上,让用户看到最新的内容。

4、比照新旧Virtual DOM树

下面咱们说到Vue会依据渲染函数生成新的虚构DOM树,而后通过比对新旧虚构DOM树来判断哪些节点须要更新。那Vue又是通过什么来做这个判断的呢?答案就是Virtual DOM Diff算法

VueVirtual DOM Diff算法是一个用于比拟新旧Virtual DOM树之间差别的算法。它通过比照两棵树的节点,找到须要更新、增加和删除的节点,而后对实在DOM进行最小化的操作。

VueDiff算法能够大抵分为以下三个步骤:

首先比拟新旧Virtual DOM根节点,如果它们的标签名key属性都雷同,则认为这两个节点是雷同的,能够间接比拟它们的子节点。如果根节点不同,则间接将新的Virtual DOM替换旧的Virtual DOM

比照子节点,通过遍历新旧Virtual DOM树的子节点列表,按程序进行比拟。Vue应用了一些启发式的策略来尽可能地缩小比拟次数,例如只对同级节点进行比拟,通过节点的key来疾速定位到雷同的节点等。在比拟过程中,如果有子节点不同,就将新的子节点插入到旧的子节点的地位上,而后递归对新旧子节点进行比拟。

比照实现后,失去了须要更新、增加和删除的节点,Vue会将这些操作打包成一个补丁(patch),而后将补丁利用到实在DOM上,从而实现更新。

上面是一个简略的Vue组件的代码示例,以阐明Diff算法的实现形式:

<template>
  <div>
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: 'Todo List',
      items: [
        { id: 1, text: 'Learn Vue' },
        { id: 2, text: 'Build an app' },
        { id: 3, text: 'Deploy to production' },
      ],
    };
  },
  methods: {
    addItem() {
      this.items.push({ id: 4, text: 'Test' });
    },
  },
};
</script>

在这个例子中,当调用addItem办法增加新的item时,Vue会从新执行渲染函数,生成一个新的Virtual DOM树。Vue会比照新旧Virtual DOM树的差别,并将差别利用到实在的DOM树上,从而实现高效的更新。

Vue2Vue3Virtual DOM Diff算法有一些不同之处,Vue3Diff算法进行了一些优化,以进步渲染性能和缩小内存占用。上面别离介绍Vue2Vue3Diff算法。

(1)、Vue2的Diff算法:

Vue2Diff算法应用了一种称为双端指针的策略,通过同时遍历新旧Virtual DOM树,从而缩小比拟次数,进步性能。具体来说,算法的过程如下:

  • 1、如果新旧节点是同一个节点,间接比拟它们的子节点。
  • 2、如果新旧节点不是同一个节点,且它们都有子节点,则将旧节点的子节点依照key映射到一个对象上,而后遍历新节点的子节点列表,通过key从旧节点的映射对象中找到对应的节点进行比拟,找不到则创立新节点。
  • 3、如果新旧节点不是同一个节点,且旧节点没有子节点,间接用新节点替换旧节点。
  • 4、如果新旧节点不是同一个节点,且新节点没有子节点,删除旧节点。

(2)、Vue3的Diff算法:

Vue3Diff算法采纳了一种称为“动静布局”的策略,通过寻找最长递增子序列,从而缩小比拟次数,进步性能。具体来说,算法的过程如下:

  • 1、如果新旧节点是同一个节点,间接比拟它们的子节点。
  • 2、如果新旧节点不是同一个节点,且它们都有子节点,则应用“最长递增子序列”算法对子节点进行匹配,将不须要更新的节点复用,须要更新的节点打上标记。
  • 3、如果新旧节点不是同一个节点,且旧节点没有子节点,间接用新节点替换旧节点。
  • 4、如果新旧节点不是同一个节点,且新节点没有子节点,删除旧节点。

上面是一个简略的Vue3组件的代码示例,以阐明Diff算法的实现形式:

<template>
  <div>
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </ul>
  </div>
</template>

<script>
import { reactive } from 'vue';

export default {
  setup() {
    const state = reactive({
      title: 'Todo List',
      items: [
        { id: 1, text: 'Learn Vue' },
        { id: 2, text: 'Build an app' },
        { id: 3, text: 'Deploy to production' },
      ],
    });

    const addItem = () => {
      state.items.push({ id: 4, text: 'Test' });
    };

    return { state, addItem };
  },
};
</script>

在这个例子中,咱们应用了Vue3的新个性setup()函数来定义组件。当调用addItem办法增加新的item时,Vue会从新执行setup()函数,生成一个新的Virtual DOM树。Vue会比照新旧Virtual DOM树的差别,并将差别利用到实在的DOM树上,从而实现高效的更新。

5、更新DOM

Vue执行完渲染函数并且比照新旧的vnode树后,如果发现须要更新DOM,那么就会执行patch函数来实现DOM更新。

patch函数定义在src/core/vdom/patch.js中,其次要作用是比拟新旧节点的差别,而后进行最小化的DOM操作,从而尽可能减少DOM操作的次数,进步渲染效率。

上面是patch函数的简化代码示例:

function patch(oldVnode, vnode) {
  if (sameVnode(oldVnode, vnode)) {
    // 如果新旧节点雷同,执行updateChildren函数更新子节点
    updateChildren(oldVnode, vnode)
  } else {
    // 如果新旧节点不同,间接替换旧节点
    const parent = api.parentNode(oldVnode.elm)
    api.insertBefore(parent, createElm(vnode), api.nextSibling(oldVnode.elm))
    api.removeChild(parent, oldVnode.elm)
  }
}

在代码示例中,updateChildren函数用于更新新旧节点的子节点,它的实现过程就是通过比拟新旧节点的子节点,找出它们之间的差别,而后进行最小化的DOM操作。如果新旧节点自身不同,则间接通过DOM操作替换旧节点。

总之,patch函数的次要作用就是通过最小化的DOM操作,将新的vnode树渲染成实在的DOM树,并将其插入到HTML文档中。

总结

本篇文章简要的聊了聊Vue的整个渲染过程,一共经验了5个步骤:

1、模板解析,通过htmlparser2的库来解析模板生成蕴含指令节点、属性节点和文本节点的AST树。

2、生成渲染函数,经验两个小的步骤,先动态剖析AST,再转换为渲染函数。

3、执行渲染函数,通过调用虚构 DOMpatch 办法来实现。

4、比照新旧虚构 DOM,介绍了Vue2Vue3 diff 算法的异同。

5、最初通过path函数更新DOM

欢送关注,公众号回复【vue的渲染过程】获取文章的全副脑图资源。

对于我 & Node交换群

大家好,我是 Quixn,专一于 Node.js 技术栈分享,前端从 JavaScript 到 Node.js,再到后端数据库,优质文章举荐。如果你对 Node.js 学习感兴趣的话(后续有打算也能够),能够关注我,加我微信【 Quixn1314 】,拉你进交换群一起交换、学习、共建,或者关注我的公众号【 小Q全栈指南 】。Github 博客开源我的项目 github.com/Quixn…

欢送加我微信【 Quixn1314 】,拉你 进 Node.js 高级进阶群,一起学Node,长期交流学习…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理