◆

Vue3 源码:运行时与编译时的区别及设计原理

2023-02-02 01:370

运行时(runtime)

什么是运行时

运行时可以利用 render 把 vnode 渲染成真实 dom 节点

简易代码演示

利用 vue 的 render 和 vnode 渲染一个 dom

<html>
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.45/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { render, h } = Vue;

      const vnode = h('div', {}, 'content');

      const container = document.getElementById('app');

      render(vnode, container);
    </script>
  </body>
</html>

如上代码我们引入了 vue.js 并从 vue 中解构了 render 和 h 函数,通过 render 最终渲染出了 vnode 中的 content。那么这个 render 以及 h 函数是什么呢。

简易版 render 与 h 函数

如下代码,我们写了一个 render 函数,h 函数则被替换成了一个对象,由此可见,vue 中提供的 render 与 h 就是一个将内容渲染到元素中的一个方法。

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div id="app"></div>
    <script>
      const vnode = {
        type: 'div',
        props: {
          class: 'test',
        },
        children: 'hello world',
      };

      const container = document.getElementById('app');

      function render(vnode) {
        const ele = document.createElement(vnode.type);
        ele.className = vnode.props.class;
        ele.innerText = vnode.children;
        document.body.appendChild(ele);
      }

      render(vnode);
    </script>
  </body>
</html>

编译时(compiler)

由上述的运行时代码所见,无论是我们自己抒写 vnode 还是用 h 函数去写都是一个很繁琐的过程,那么有没有什么简单的方法,直接在 html 标签结构去抒写代码呢。

示例代码

<html>
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.45/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { compile, createApp } = Vue;

      const html = `
    <div class="test">hello world</div>
  `;

      const renderFn = compile(html);

      console.log(renderFn);

      const app = createApp({
        render: renderFn,
      });

      app.mount('#app');
    </script>
  </body>
</html>

如上代码我们从 vue 中解构了 compile,createApp,我们定义了一个类似与 html 结构的 html 字符串,然后调用了 compile 函数去编译这个字符串。

renderFn 打印后的结果如下

什么是运行时编译时

可以看出来 renderFn 最后返回的也就是一个 render 函数,最终我们通过 createApp 创建实例,加入 render 方法,最终挂载在 #app 下渲染成功

所以对编译时可以理解为:可以把一个 html 的节点,编译成 render 函数。

同时我们可以看出,vue 是一个运行时+编译时框架,通过 compile 解析 html 模板,生成 render 函数, 然后通过 runtime 解析 render,从而挂载真实 dom

vue 为什么要设计成一个运行时+编译时的框架呢

dom 是如何渲染的

1.初次渲染:我们可以把它叫做挂载 2.更新渲染:我们可以把它叫做打补丁

初次渲染

当初始 div 的 innerHTML 为空时

我们在该 div 中渲染如下节点

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

这样的一次渲染就是初始渲染,在这样的一次渲染中,我们会生成一个 ul 标签,同时生成三个 li 标签,并且把他们挂载到 div 中。

更新渲染

如果此时 ul 的标签内容发生了变化

<ul>
  <li>3</li>
  <li>1</li>
  <li>2</li>
</ul>

那么这样的一次更新, 浏览器如何去更新这次渲染呢?方法有两种

  1. 删除原有的所有节点,重新渲染新的节点
  2. 删除原位置的 li - 3,在新的位置插入 li - 3

那么上述两种方式,哪一种方式更好呢?

  1. 对于第一种方式,不需要任何比对,只需要执行 6 次(删除 3 次,重新渲染 3 次 ) dom 操作。
  2. 对于第二种方式,在逻辑上需要分成两个步骤
  • 对比旧节点和新节点之间的差异
  • 根据差异,删除一个旧节点,增加一个新节点

根据上述的分析,我们知道了

  1. 第一种方式涉及更多的 dom 操作
  2. 第二种方式涉及到 js 计算 + 少量的 dom 操作

那么两种方式到底那种更快呢

测试 dom 操作与 js 操作到底哪个快

示例代码

<html>
  <body></body>
  <script>
    const length = 10000;

    console.time('element');

    for (let i = 0; i < length; i++) {
      const div = document.createElement('div');
      document.body.appendChild(div);
    }

    console.timeEnd('element');

    console.time('js');
    const divList = [];
    for (let i = 0; i < length; i++) {
      const ele = {
        type: 'div',
      };
      divList.push(ele);
    }

    console.timeEnd('js');
  </script>
</html>
什么是运行时编译时

如上述结果所示,js 操作比 dom 操作快了 10 倍左右的时间,所以可想而知,dom 的操作要比 js 的操作耗时多得多, 即:dom 操作比 js 更加耗费性能

比对

  1. 针对纯运行时,因为不存在编辑器,我们只能提供一个复杂的 js 对象,开发者很难去一直写这种大量的 js 对象来去表示 html
  2. 针对纯编译时,因为缺少运行时,所以他只能把分析差异的操作放到编译时进行,同样因为省略了运行时,所以速度可能会更快,但这种方式将损失灵活性,如 sevlte 框架,纯编译时框架
  3. 运行时+编译时,如 vue 与 react,使其保持灵活性的基础上,尽可能的优化性能
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue3 源码:运行时与编译时的区别及设计原理加载中