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
相关推荐
别拿曾经看以后~1 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9152 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍