Vue 进阶系列丨虚拟DOM和VNode

本文详细介绍了Vue.js中虚拟DOM的概念及其工作原理。包括虚拟DOM如何提高渲染效率、VNode的作用及类型,以及Vue.js从1.0到2.0版本在虚拟DOM处理方式上的演变。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!


2013年7月28日,尤雨溪第一次在 GItHub 上为 Vue.js 提交代码;2015年10月26日,Vue.js 1.0.0版本发布;2016年10月1日,Vue.js 2.0发布。

最早的 Vue.js 只做视图层,没有路由, 没有状态管理,也没有官方的构建工具,只有一个库,放到网页里就可以直接用了。

后来,Vue.js 慢慢开始加入了一些官方的辅助工具,比如路由(Router)、状态管理方案(Vuex)和构建工具(Vue-cli)等。此时,Vue.js 的定位是:The Progressive Framework。翻译成中文,就是渐进式框架。

Vue.js2.0 引入了很多特性,比如虚拟 DOM,支持 JSX 和 TypeScript,支持流式服务端渲染,提供了跨平台的能力等。Vue.js 在国内的用户有阿里巴巴、百度、腾讯、新浪、网易、滴滴出行、360、美团等等。

Vue 已是一名前端工程师必备的技能,现在就让我们开始深入学习 Vue.js 内部的核心技术原理吧!


什么是虚拟DOM

作为一个前端,想必或多或少都听说过虚拟 DOM 这个名词,那么虚拟 DOM到底是什么呢?

在 web 早期,页面的交互效果还是比较简单的,不需要频繁的去操作DOM,使用 Jquery 完全可以满足我们的需求。但是随着时代的发展,页面上的东西越来越多,需求也越来越复杂,相应的程序中需要维护的状态也越来越多,我们就要不断的去操作 DOM。此时命令式编程的 Jquery 就显得很臃肿,后期维护困难。

三大框架的诞生就是为了解决这一问题,声明式操作 DOM,我们只需要描述状态和DOM之间的映射关系,就可以将状态渲染成视图。从状态到视图的过程,框架会帮我们做,不需要我们手动去操作DOM。

当状态发生变化时,需要重新渲染页面,也就是框架要去帮助我们操作DOM,那么框架怎么去确定状态发生了什么变化以及框架要去哪里更新DOM 呢?最简单的做法就是只要有一个状态发生了变化,页面里全部的DOM 元素都重新渲染一遍,但是访问 DOM 的操作是比较费时的,如果按照这么做的话,会造成相当大的浪费。那么 Vue.js 是怎么做的呢?

Vue.js 在渲染页面之前会先根据状态生成一份虚拟 DOM 树,然后使用虚拟DOM 树进行渲染,在渲染之前,会使用新生成的虚拟 DOM 树和上一次生成的虚拟 DOM 树进行对比,只渲染不同的部分。


Vue的虚拟DOM

在 Vue.js1.0 的时候,Vue 并没有引用虚拟 DOM,当时的做法是当状态发生变化是,Vue 已经知道了是哪个节点使用了这个状态,然后将这个节点进行更新操作,根本不需要对比操作。

但是这样的缺点是,对于每一个状态都有一个依赖跟踪器,粒度太细,这样就会有一些内存开销和依赖追踪的开销,当状态被越来越多的节点使用的时候,开销就会非常大。

Vue2.0 的时候选择了一个中等粒度,当状态发生变化的时候,只在组件级别有一个依赖跟踪器,当组件内的状态改变的时候,只能通知到组件层次,组件内部通过虚拟 DOM 进行比对与渲染。Vue 之所以能随意的切换绑定的粒度,本质上还要归功于变化侦测,关于变化侦测的相关知识请移步Vue 进阶系列丨Object 的变化侦测

Vue 使用模板来描述状态与 DOM 之间的映射关系,通过编译将模板转换成渲染函数,执行渲染函数生成虚拟节点树(vnode),使用虚拟节点数就可以渲染页面了。当状态发生变化的时候,直接进行 DOM 操作会比较慢,所以首先会先生成虚拟节点树,将新的节点树和旧的节点树进行对比,然后再操作 DOM,更新页面。

所以,虚拟 DOM 主要做了两件事:

  • 提供与真实 DOM 节点所对应的虚拟节点 vnode

  • 将新的虚拟节点和旧的虚拟节点进行对比,然后更新页面


什么是 VNode

VNode 是 Vue.js 的一个类,使用它可以实例化不同的 vnode 实例,我们只需要控制传入的参数的不同就可以了。

class VNode {
  constructor (
    tag,
    data,
    children,
    text,
    elm,
    context,
    componentOptions,
    asyncFactory
  ) {
    this.tag = tag;
    this.data = data;
    this.children = children;
    this.text = text;
    this.elm = elm;
    this.ns = undefined;
    this.context = context;
    this.fnContext = undefined;
    this.fnOptions = undefined;
    this.fnScopeId = undefined;
    this.key = data && data.key;
    this.componentOptions = componentOptions;
    this.componentInstance = undefined;
    this.parent = undefined;
    this.raw = false;
    this.isStatic = false;
    this.isRootInsert = true;
    this.isComment = false;
    this.isCloned = false;
    this.isOnce = false;
    this.asyncFactory = asyncFactory;
    this.asyncMeta = undefined;
    this.isAsyncPlaceholder = false;
  }
  get child () {
    return this.componentInstance
  }
}

我们可以看到 vnode 仅仅只是 VNODE 类的一个实例而已,通过传入的参数不同,就可以生成不同类型的 vnode 实例对象了。vnode 可以理解成节点描述对象,它描述了应该怎样去创建真实的 DOM 节点。


VNode 的种类

  • 注释节点

const createEmptyVNode = (text = '') => {
  const node = new VNode();
  node.text = text;
  node.isComment = true;
  return node
};

我们可以看到注释节点只有两个有效属性,text 和 isComment,其余属性都是默认的 undefined 或者 false。

例如,一个真实的注释节点:

<!-- 注释节点 -->

所对应的 vnode 是这样的:

{
  text:'注释节点',
  isComment:true
}
  • 文本节点

function createTextVNode (val) {
  return new VNode(undefined, undefined, undefined, String(val))
}

文本节点只有一个 text 属性,它的 vnode 是这样的:

{
  text:'我是文本内容',
}
  • 克隆节点

function cloneVNode (vnode) {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children && vnode.children.slice(),
    vnode.text,
    vnode.elm,
    vnode.context,
    vnode.componentOptions,
    vnode.asyncFactory
  );
  cloned.ns = vnode.ns;
  cloned.isStatic = vnode.isStatic;
  cloned.key = vnode.key;
  cloned.isComment = vnode.isComment;
  cloned.fnContext = vnode.fnContext;
  cloned.fnOptions = vnode.fnOptions;
  cloned.fnScopeId = vnode.fnScopeId;
  cloned.asyncMeta = vnode.asyncMeta;
  cloned.isCloned = true;
  return cloned
}

克隆节点就是将一个节点的内容全部复制到新的节点中。主要用来优化静态节点和插槽节点。当静态节点需要重新渲染的时候,并不需要重新再次走一遍渲染函数,然后重新生成 vnode,只需要使用克隆节点的方法将之前的 vnode 克隆一份即可,一定程度上优化了程序的性能。

克隆节点和被克隆节点的唯一区别是 isCloned 属性,克隆节点的 isCloned为 true,被克隆的原始节点的 isCloned 为 false。

  • 元素节点

元素节点一般包含 4 个有效属性:

  1. tag:就是节点的名称,例如 div、p、img

  2. data:该属性包含一些节点上的数据,例如 class、style

  3. children:该节点的子节点列表

  4. context:当前组件的 vue.js 实例

通常,函数式组件的 vnode 是这样子的:

{
  children:[VNode,VNode],
  context:{...},
  data:{...},
  tag:{...}
}
  • 组件节点

和元素节点类似,有两个独有的属性 componentInstance 和 componentOptions。

通常,函数式组件的 vnode 是这样子的:

{
  componentInstance:{...},
  componentOptions:{...},
  context:{...},
  data:{...},
  tag:{...}
}
  • 函数式组件

和组件节点类似,有两个独有的属性 functionalContext 和 functionalOptions。

通常,函数式组件的 vnode 是这样子的:

{
  functionalContext :{...},
  functionalOptions:{...},
  context:{...},
  data:{...},
  tag:{...}
}

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

叶阳辉

HFun 前端攻城狮

往期精彩:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值