夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

    • MVVM模式
    • 生命周期
    • 修饰符
    • script setup(Vue3)
    • 片段(Vue3多根节点的概念)
    • Vue监测属性的原理
    • 计算属性 vs 方法 vs 侦听属性
    • v-if vs v-show
    • 列表渲染之数组、对象更新检测
    • style特性
    • style中的v-bind
    • 渲染机制
      • 1.模板解析
        • 2、生成渲染函数
      • 3.挂载
      • diff算法
  • 响应式

  • 组件

  • 过渡&动画

  • 可复用性

  • 工具

  • 规模化

  • 路由(核心)

  • 组合式

  • Vuex

  • Pinia

  • 其他

  • Vue3 源码学习记录

  • 《Vue》笔记
  • 基础
夜猫子
2022-06-28
目录

渲染机制

# 渲染机制

Vue的整个渲染过程,一共经历了5个步骤:

  1. 模板解析,通过htmlparser2的库来解析模板生成包含指令节点、属性节点和文本节点的AST树(抽象语法树)。

  2. 生成渲染函数,先进行 AST 静态分析,再转换为渲染函数(render 函数)。

  3. 挂载(mount),挂载时会执行渲染函数,并生成 VNode(虚拟节点),并基于此创建实际的 DOM 节点。

  4. 更新,执行 diff 算法,然后将必要的更新应用到真实 DOM 上去。。

img

# 1.模板解析

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

AST 是一种树状的数据结构,表示了模板中各个语法结构之间的关系。语法分析的目的是理解模板的结构,为后续的优化和代码生成提供基础。

下面是Vue模板解析的基本流程:

1.解析

编译器首先会对Vue模板进行解析,将模板字符串解析为抽象语法树(AST)。AST是一种以对象的形式表示模板结构的数据结构。

2.静态分析

在此阶段,编译器会对AST进行静态分析,收集模板中的所有静态节点(不依赖组件实例数据的节点)。这样的静态节点可以在每次重新渲染时重复使用,从而提高性能。

# 2、生成渲染函数

解析完模板后,Vue将AST转换为渲染函数(在编译阶段就完成了)。渲染函数是一个JavaScript函数,这个渲染函数接受组件实例的状态作为参数,然后执行渲染逻辑,生成 VNode。

下面是一个简单的示例,假设我们有这样一个 Vue 模板:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="handleClick">Click Me</button>
  </div>
</template>
1
2
3
4
5
6

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

这是一个打印出来的组件对象:

image-20241101115909077

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")
  ]))
}
1
2
3
4
5
6
7

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

# 3.挂载

挂载时会首次执行渲染函数,渲染函数会根据当前的渲染上下文数据生成虚拟DOM节点。

render: function (createElement) {
    return createElement(
		"div", { innerHtml: "456" }
    )
  }
1
2
3
4
5

上面的代码执行后,会返回 vnode ,大概结构是这样的:

image-20240628153853609

提示

createElement 别名 h 函数,h 函数接受三个参数:节点类型(字符串或组件)、属性对象、子节点,是 vue 提供的用于创建 vnode 的辅助函数。

# diff算法

diff 算法,也称为 “patch” 算法,是一种智能比较 Virtual DOM 的算法,旨在最小化对真实 DOM 的操作次数,提高渲染性能。

img

步骤:

  1. 节点比对:对新旧 VNode 进行深度优先遍历,比较节点类型、key、data 等,找出相同节点和需要更新的节点。
  2. 对比完成后,得到了需要更新、添加和删除的节点,Vue会将这些操作打包成一个补丁(patch)然后将补丁应用到真实DOM上,从而完成更新。

实现:

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

判断是否是同一个节点

  • 判断两个VNode节点是否是同一个节点,需要满足以下条件

  • key相同

  • tag(当前节点的标签名)相同

  • isComment(是否为注释节点)相同

  • 是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义

当标签是<input>的时候,type必须相同

:::

# 更新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)
  }
}
1
2
3
4
5
6
7
8
9
10
11

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

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

编辑 (opens new window)
上次更新: 2024/11/1 18:04:12
style中的v-bind
Vue3 Proxy 响应式原理

← style中的v-bind Vue3 Proxy 响应式原理→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式