Vue 知识篇(2):浅谈Vue中的DOM与VNode

DOM,相信无论是初入前端的小伙伴还是已经沉浸在前端多年的大佬,都对这个东西不会很陌生,但是对于这个东西很多人只知其然不知其所以然,所以今天我们来刨析一个这个跟多我打了很多年交道的老朋友。

一、DOM 节点的基本概念

1.1 DOM 节点的基本概念

DOM,英文全称是[Document Object Model],翻译过来就是文档对象模型 ,DOM节点是构成网页文档结构的基本单位,是 HTML/XML 文档中每个组成部分的对象表示,浏览器通过 DOM 节点构建文档的树形结构 DOM 树。

🧩 DOM节点就像乐高积木,想象你在玩乐高玩具:

  • 每个单独的乐高积木块就是一个DOM节点
  • 你把积木拼成房子、车子,就像DOM节点组成网页
  • 大积木可以套小积木,就像<div>里面可以放<p>

1.2 DOM主要节点类型

DOM中,nodeType 的值用于区分不同类型的节点。这些值是预定义的常量,可以帮助开发者在操作 DOM 时准确地识别和处理不同类型的节点。

类型 说明
元素节点 1 HTML 标签(如 <div><p>
属性节点 2 HTML 属性(如 class="title"
文本节点 3 元素内的文本内容
注释节点 8 HTML 注释(<!-- 注释 -->
文档节点 9 整个文档(document
文档类型节点 10 <!DOCTYPE html>
  1. 元素节点(Element Node)
  • :1
  • 说明 :表示HTML标签,如 <div><p> 等。
  • 用途 :当你需要操作HTML元素(如获取元素的属性、修改元素的内容等)时,可以通过检查节点类型是否为1来确定它是一个元素节点。
  1. 属性节点(Attribute Node)
  • :2
  • 说明 :表示HTML属性,如 class="title"
  • 用途 :虽然在现代DOM操作中,属性通常通过元素的 attributes 属性来访问,但在某些情况下,你可能需要检查节点类型是否为2来确定它是一个属性节点。
  1. 文本节点(Text Node)
  • :3
  • 说明:表示元素内的文本内容。
  • 用途:当你需要获取或修改元素内的文本内容时,可以通过检查节点类型是否为3来确定它是一个文本节点。
  1. 注释节点(Comment Node)
  • :8
  • 说明 :表示HTML注释,如 <!-- 注释 -->
  • 用途:在某些情况下,你可能需要处理HTML注释,例如在解析或生成HTML代码时,可以通过检查节点类型是否为8来确定它是一个注释节点。
  1. 文档节点(Document Node)
  • :9
  • 说明 :表示整个文档,即 document 对象。
  • 用途:当你需要操作整个文档(如获取文档的根节点、设置文档的标题等)时,可以通过检查节点类型是否为9来确定它是一个文档节点。
  1. 文档类型节点(Document Type Node)
  • :10
  • 说明 :表示文档类型声明,如 <!DOCTYPE html>
  • 用途 :在某些情况下,你可能需要检查文档类型声明,例如在解析或生成HTML代码时,可以通过检查节点类型是否为10来确定它是一个文档类型节点。

1.3 DOM 节点的核心属性

DOM中,所有节点都具有一些通用属性,这些属性可以帮助我们获取节点的基本信息和关系。

属性 说明
nodeName 节点名称(标签名大写)
nodeType 节点类型(数字值)
nodeValue 节点值(文本/注释节点才有)
childNodes 所有子节点的集合
parentNode 父节点
previousSibling 前一个兄弟节点
nextSibling 后一个兄弟节点

1 nodeName

  • 说明 :节点名称。对于元素节点,它是标签名的大写形式;对于属性节点,它是属性名;对于文本节点,它是#text;对于注释节点,它是#comment;对于文档节点,它是#document

  • 用途 :通过nodeName可以快速获取节点的类型或名称。
    2 nodeType

  • 说明 :节点类型,是一个数字值,表示节点的类型。常见的节点类型及其值如1.2所述

  • 用途 :通过nodeType可以判断节点的类型,从而进行相应的操作。
    3 nodeValue

  • 说明 :节点的值。对于文本节点,它是文本内容;对于注释节点,它是注释内容;对于属性节点,它是属性值。其他类型的节点nodeValue通常为null

  • 用途 :通过nodeValue可以获取或设置节点的值。
    4 childNodes

  • 说明 :一个NodeList对象,包含当前节点的所有子节点。

  • 用途 :通过childNodes可以遍历当前节点的所有子节点,进行操作或查询。
    5 parentNode

  • 说明 :当前节点的父节点。如果当前节点是文档的根节点,则parentNodenull

  • 用途 :通过parentNode可以获取当前节点的父节点,从而进行向上级的查询或操作。
    6 previousSibling

  • 说明 :当前节点的前一个兄弟节点。如果没有前一个兄弟节点,则为null

  • 用途 :通过previousSibling可以获取当前节点的前一个兄弟节点,进行同级的查询或操作。
    7 nextSibling

  • 说明 :当前节点的后一个兄弟节点。如果没有后一个兄弟节点,则为null

  • 用途 :通过nextSibling可以获取当前节点的后一个兄弟节点,进行同级的查询或操作。

1.4 DOM 节点的操作

我们在讲DOM节点的类型时候讲过类型的值为9的节点叫文档节点(Document Node)documentDOM中的一个核心对象,它代表了整个HTML文档。通过document对象,可以访问和操作文档中的所有元素和属性。它是整个DOM树的根节点,是与页面内容交互的入口点。

方法 描述
getElementById() 通过 ID 获取单个元素
querySelector() 通过 CSS 选择器获取第一个匹配元素
querySelectorAll() 通过 CSS 选择器获取所有匹配元素
createElement() 创建新的 HTML 元素
createTextNode() 创建文本节点
createDocumentFragment() 创建文档片段(性能优化用)
write() 向文档流写入内容
addEventListener() 添加事件监听器
removeEventListener() 移除事件监听器
hasFocus() 检查文档是否获得焦点

关于DOM节点的操作我将列举其主要函数等,但是并不会深入探讨,现在前端的主流框架VUE等是数据驱动视图 ,通过document直接操作DOM违背了Vue 的设计原则。

现在框架中为什么不推荐直接操作 DOM?
  1. 违背响应式原则

    Vue 的响应式系统会自动跟踪数据变化并更新 DOM,直接操作 DOM 会导致视图与数据状态不同步。

  2. 破坏组件封装性

    直接操作其他组件的 DOM 可能引发不可预期的副作用,降低代码可维护性。

  3. 性能优化失效

    Vue 的虚拟 DOM(Virtual DOM)会高效批量更新真实 DOM,直接操作会绕过这一优化机制。

  4. SSR/跨平台兼容性问题

    服务端渲染(SSR)或非浏览器环境(如 Weex)中 document 对象不存在,直接操作会导致错误。

  5. DOM 操作是昂贵的

    每次修改 DOM(如修改样式、添加/删除节点),浏览器需要重新计算布局并重新绘制,这会消耗大量 CPU/GPU 资源。

html 复制代码
<template>
  <div ref="myDiv">Hello Vue</div>
</template>

<script>
export default {
  mounted() {
    // 通过 $refs 访问而不是 document
    this.$refs.myDiv.textContent = 'Updated content';
  }
}
</script>

二、VNode

2.1 什么是 VNode?

VNode全称为[Virtual Node],中文名称虚拟节点 ,是 Vue 用来描述真实 DOM 节点的 JavaScript 对象。它相当于真实 DOM 的轻量级"蓝图 "。你可以把VNode想象成建筑师的"设计图纸 ",而真实DOM就是实际建好的房子。

2.2 VNode 的核心属性

属性 类型 说明
tag String HTML标签名或组件名
data Object 包含class, style, attrs
children Array 子VNode数组
text String 文本节点的内容
elm DOM Element 对应的真实DOM节点
key String/Number 用于Diff算法的唯一标识
  1. tag

    • 类型:String
    • 说明:
      表示 HTML 原生标签名(如 "div""span")或注册的组件名(如 "MyComponent")。
      • 原生标签会渲染为对应的 DOM 元素
      • 组件名会触发组件实例化流程
  2. data

    • 类型:Object

    • 说明:
      包含节点的配置数据,常用字段包括:

      javascript 复制代码
      {
        class: 'active',    // CSS 类名
        style: { color: 'red' },  // 行内样式
        attrs: { id: 'app' },     // HTML 特性
        on: { click: handler }    // 事件监听
      }
  3. children

    • 类型:Array<VNode>
    • 说明:
      当前节点的子节点数组,支持嵌套结构。特殊说明:
      • 空数组表示无子元素
      • 文本节点可用字符串直接表示(如 ['文本']
  4. text

    • 类型:String

    • 说明:
      专为文本节点设计的属性,与 tag 互斥。例如:

      javascript 复制代码
      { text: '纯文本内容' }  // 等效于 document.createTextNode()
  5. elm

    • 类型:DOM Element
    • 说明:
      在 patch 阶段由框架自动挂载,指向该 VNode 对应的真实 DOM 节点。开发者通常无需手动操作。
  6. key

    • 类型:String | Number
    • 说明:
      Diff 算法的核心优化标识,适用于:
      • v-for 列表渲染(避免就地复用问题)
      • 动态组件切换(强制触发生命周期)

2.3 为什么要使用VNode

  1. 性能优化

    • 减少直接操作DOM的次数
      直接操作DOM会触发浏览器重排和重绘,性能消耗较大。VNode通过在内存中操作虚拟DOM,批量处理真实DOM更新,减少渲染开销。
    • 高效的更新机制
      通过Diff算法(如Vue的Snabbdom)对比新旧VNode差异,仅更新必要的DOM节点,避免全量渲染。
  2. 提高开发效率

    • 声明式编程
      用声明式描述UI状态(如Vue模板/React JSX),替代手动操作DOM的命令式代码,提升可维护性。
    • 组件化开发
      支持将UI拆分为可复用组件(如.vue文件/React组件),简化复杂界面开发。
  3. 跨平台支持

    • 跨浏览器兼容
      基于JavaScript标准实现,不依赖特定浏览器API,兼容Chrome/Firefox/Safari等。
    • 跨平台扩展
      虚拟DOM可应用于:
      • 服务端渲染(SSR):如Nuxt.js/Next.js
      • 移动端:React Native/Weex
      • 桌面端:Electron
特性 设计图纸(VNode) 真实房子(DOM)
表示方式 用纸上的线条和标注表示房子 是实际建好的砖瓦结构
修改难度 修改起来非常快(擦掉重画就行) 修改代价高(拆墙重建很费劲)
成本 成本低(就是一张纸) 成本高(要用真实建筑材料)
性能优化 减少直接操作DOM的次数,高效的更新机制 直接操作DOM,性能较低
开发效率 声明式编程,组件化开发,代码更简洁、易于维护 手动操作DOM,代码复杂,维护成本高
跨平台支持 跨浏览器兼容,支持服务器端和移动设备 依赖于浏览器,不支持跨平台

2.4 VNode与DOM 的关系

VNode 本质是一个普通的 JavaScript 对象,用来描述 DOM节点,它的作用是VUE在内存中维护一个虚拟 DOM树(由多个 VNode 组成),用于高效计算 DOM 的更新。

javascript 复制代码
// 一个简单的 VNode 示例
{
  tag: 'div',          // 标签名
  props: { class: 'container' },  // 属性(如 class, id)
  children: [          // 子节点
    { tag: 'p', children: 'Hello World' }
  ]
}

// 对应的真实DOM节点
<div class="container">
  <p>Hello World</p>
</div>

三、VNode 的创建过程

3.1 VNode 如何变成真实 DOM

Vue 的渲染流程:

  • 模板编译:Vue 模板(如 .vue 文件)会被编译成 渲染函数(render function)。

  • 生成 VNode:渲染函数执行后,返回一个 VNode 树(虚拟 DOM)。

  • Diff 比对:当数据变化时,Vue 会生成新的 VNode,并和旧的 VNode 进行对比(Diff 算法)。

  • 更新 DOM :只修改真正变化的部分(避免全量更新 DOM,提高性能)。

3.2 手动创建

在深入探讨之前,我们需要先了解 Render 函数 的概念。Render 函数 是一个接收 createElement 方法(通常简写为 h)作为参数的函数,它返回一个虚拟 DOM 节点(VNode),用于描述组件的渲染结构。

createElement(即 h 函数)是 Vue 渲染机制的核心,专门用于创建 VNode。其有三个接收参数标签名(必需)数据对象(可选)子节点(可选)基本语法如下:

javascript 复制代码
// HTML 标签
createElement('div')

// 组件
createElement(MyComponent)

// 文本子节点
createElement('div', 'hello world')

// 数组子节点
createElement('div', [
  createElement('span', 'hello'),
  createElement('span', 'world')
])

关于 Render 函数与 createElement 相关的知识点也是非常的密集和重要,因此放在同一个篇章肯定是讲不完了,所以后续会详细的讲解一些关于这两个的使用。

相关推荐
kunge1v53 小时前
学习爬虫第三天:数据提取
前端·爬虫·python·学习
可爱的秋秋啊3 小时前
简单网站编写
开发语言·前端
Keepreal4963 小时前
Electron基本概念
前端·javascript·electron
zhaoolee3 小时前
Claude Code使用指北(如何白嫖百万Qwen3 Token,每月劲省20刀)
前端
前台端水工程师3 小时前
vite-plugin-mock插件的3.0.2版本在生产环境无法使用
前端
戈卬3 小时前
VSCode 中 Prettier 工作原理与使用指南
前端
我叫张得帅3 小时前
从零开始的前端异世界生活--005--“HTTP详细解析中”
前端
Whbbit19993 小时前
在 Nestjs 中使用 Drizzle ORM
前端·javascript·nestjs
Never_Satisfied3 小时前
在JavaScript中,map方法使用指南
前端·javascript·vue.js