Vue3源码梳理:关于运行时和编译时

运行时和编译时的概念

  • vue中的html是真实的html么?

  • .vue文件是单文件组件, 在template中写入的html是真实的html标记节点么?

    html 复制代码
    <template>
      <div v-if='false'>hello</div>
      <m-com />
    </template>
  • 上述 v-if 和 m-com 自定义组件不是正常的html, 浏览器是不认识的,所以不会解析

  • 上面两个两个问题的答案都是否定的

  • 但是我们的浏览器正确渲染了结果,这里vue一定在中间做了处理

  • 这个过程主要是:让假标记,被渲染成真实的html标签,最终呈现给用户

  • 从这方面来说,Vue的核心功能,分为三项:

    • 运行时:runtime
    • 编译时:compiler
    • 运行时 + 编译时: runtime + compiler
  • vue的响应式系统是一个 运行时 + 编译时 结合的复合系统

  • 这三个概念早就提出过,vue也是使用了这个方案

运行时

  • vuejs/core/tree/main/packages

    • runtime-core
      • 这里对外暴露出一个函数:render
      • 用于编程式地创建虚拟DOM树的函数
      • 作用是:代替template模板来完成 DOM 的渲染
    • runtime-dom
  • 使用render函数渲染出一个div

    html 复制代码
    <script src='vue.js'></script>
    <div id="app"></div>
    <script>
      const { render, h } = Vue
      // 生成vnode
      const vnode = h('div', {class: 'test'}, 'hello render')
      // 找到div容器
      const box = document.querySelector('#app')
      // 渲染
      render(vnode, box)
    </script>

render函数内部如何实现

假如基于的一个json想要渲染出一个页面, 应该如何实现

json如下

js 复制代码
{
  type: 'div',
  props: {
    class: test
  },
  children: 'hello render'
}

渲染出结果如下的div

html 复制代码
<div class='test'>hello render</div>

原生js实现

js 复制代码
const vnode = {
  type: 'div',
  props: {
    class: 'test'
  },
  children: 'hello render'
}

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

render(vnode)
  • 以上的代码结构,就是一个运行时的代码框架
  • 当然不考虑树的递归处理,仅仅处理这一个程序示例
  • 简单总结:运行时,利用render把 vnode 渲染成真实 dom 节点
  • 如果想通过 template 模板编写 html 的方式,就不是运行时的方式了

编译时

  • 运行时做不到的事情,通过html标签结构的方式来渲染解析成真实的dom节点

  • vue中的编译时,准确来说是编译器的意思,核心代码在 compiler-core模块下

    js 复制代码
    const { compile, createApp } = Vue
    const html = `
      <div class='test'>hello compiler</div>
    ` 
    const app = createApp({
      render: compile(html)
    })
    app.mount('#app')
  • 也就是把html字符串借助compile的方式变成render函数,再通过render函数渲染出真实dom

  • 编译器的主要作用

    • 把template的html编译成render函数,再利用 运行时
    • 基于render 挂载对应的 dom
  • 简单总结:编译时,把html的模板,编译成render函数

Vue为什么要设计成运行时和编译时这样

  • 如果 compile 函数已经可以把 模板解析了,为何还要生成一个 render 函数
  • 然后,再通过 render 函数进行挂载,这不多此一举?
  • vue为何这样做?

1 )需要先知道dom渲染是如何进行的

可分为两部分

  • 初次渲染,可以叫做挂载
  • 更新渲染,叫做打补丁

初次渲染

容器盒子

html 复制代码
<div id='app'></div>

需要渲染的节点

html 复制代码
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

初次渲染,会生成一个ul标签,同时生成三个li标签,并把他们挂载到div中

更新渲染

此时ul里的内容发生了变化

html 复制代码
<ul>
  <li>3</li>
  <li>1</li>
  <li>2</li>
</ul>

li标签发生了顺序变化,期望浏览器如何更新渲染,有两种方案:

  • a. 清空ul内容,重新渲染li节点
    • 这种简单直接,但是操作执行dom次数多
  • b. 删除原位置的 li-3,在新位置插入li-3
    • 对比旧节点和新节点之间差异,根据差异,把原先最下面的删除,再新增一个新节点
    • 这样,dom操作显著减少,js上的逻辑增多
  • 但是哪种效率更高呢

2 )下面是对比测试

2.1 )1万个dom节点的生成

html 复制代码
<body></body>
<script>
  const len = 10000
  console.time('element')
  for (let i = 0; i < len; i++) {
    const div = document.createElement('div')
    document.body.appendChild(div)
  }
  console.timeEnd('element')
</script>

2.2 )1万个js对象的生成

html 复制代码
<body></body>
<script>
  const len = 10000
  const divList = []
  console.time('js')
  for (let i = 0; i < len; i++) {
    const json = { type: 'div' }
    divList.push(json)
  }
  console.timeEnd('js')
</script>
  • 耗时数据差距非常明显,我们可以看到js的计算性能远超过dom操作
  • 这里, 即便是dom操作也有优化的空间, 比如通过DocumentFragment批量操作dom, 但是相对比之下 js 操作性能永远大于 dom 操作的性能

vue被设计成运行时和编译时框架

  • 对于纯运行时,不存在编译器,每次渲染,我们需要提供一个复杂的js对象
  • 对于纯编译时,因为缺少运行时计算,所以它只能把分析差异的操作,放到编译时进行,同样,因为省略了运行时,速度可能会更快,但是这种方式将会损失灵活性
    • 可以查看官方文档,渲染函数 & JSX渲染机制
    • 再比如,svelte, 是一个纯编译时框架,但是它实际运行速度达不到理论上的速度
  • 运行时 + 编译时:vue和react都是通过这种方式构建的,使其保持灵活性的基础上,尽量的进行性能的优化,达到一种平衡

副作用

  • 当对数据进行 setter 或 getter 操作时,所产生一系列的后果
  • 副作用可能会有多个

1 ) setter

  • 赋值操作,比如执行下面代码

    js 复制代码
    msg = 'hello world'
  • 这时 msg 就触发了一次 setter 的行为

  • 假如说, msg 是一个响应性数据,这样一次数据改变,就会影响到对应的 视图改变

  • 我们就可以说, msg 的 setter 行为,触发了一次副作用,导致视图跟随发生了变化

2 ) getter

  • getter 所表示的是取值的操作,比如,我们执行如下代码时

    js 复制代码
    element.innerText = msg
  • 对于变量 msg 而言,触发了一次 getter 操作,这一次取值操作,同样会导致 element 的 innerText发生改变

  • 所以,我们可以说:msg 的 getter 行为触发了一次副作用,导致 element 的 innerText 发生了变化

  • Vue的响应性的核心,就依赖这两个特性: getter和setter

3 ) 副作用会产生多个么

可以的

html 复制代码
<template>
  <div>
    <p>{{ obj.name }}</p>
    <p>{{ obj.age }}</p>
  </div>
</template>
<script>
  const obj = ref({
    name: '张三',
    age: 30
  })
  obj.value = {
    name: '李四',
    age: 18
  }
</script>

一次setter操作,会导致两个p标签内容变化,这就是多个副作用的体现

vue3设计核心

  • vue3的核心可分为三大模块
    • 响应性:reactivity
    • 运行时:runtime
    • 编译器:compiler

基于以下代码示例来说明

html 复制代码
<template>
  <div> {{ proxyTarget.name }} </div>
</template>

<script>
  import { reactive } from 'vue'
  export default {
    setup() {
      const target = {
        name: '张三'
      }
      const proxyTarget = reactive(target)
      return {
        proxyTarget
      }
    }
  }
</script>
  • 首先对于 响应性 reactivity 而言,对外暴露出 reactive 方法
    • 它可以接收一个复杂数据类型参数(被代理对象),返回一个 proxy 对象,也就是代理对象
    • 当 这个代理对象 proxyTarget 触发 getter 和 setter 行为时,都会产生对应的副作用
  • 对于 编译器 compiler 而言,将模板转化为 render 函数
    • 会把 template 里的div进行解析,解析出的内容变为 render 函数
  • 最后,运行时 runtime 会把compiler返回的 render 函数进行渲染,从而得出真实dom
相关推荐
安冬的码畜日常7 分钟前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
杨荧26 分钟前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
l1x1n035 分钟前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。1 小时前
案例-任务清单
前端·javascript·css
Front思1 小时前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称3 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色3 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js