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
相关推荐
吃杠碰小鸡28 分钟前
lodash常用函数
前端·javascript
emoji11111137 分钟前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼40 分钟前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_7482500344 分钟前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235951 小时前
web复习(三)
前端
迷糊的『迷』1 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot
web135085886351 小时前
uniapp小程序使用webview 嵌套 vue 项目
vue.js·小程序·uni-app
AiFlutter1 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
麦兜*1 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue