我的编程空间,编程开发者的网络收藏夹
学习永远不晚

vue 虚拟DOM快速入门

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

vue 虚拟DOM快速入门

虚拟 DOM

什么是虚拟 dom

dom 是文档对象模型,以节点树的形式来表现文档。

虚拟 dom 不是真正意义上的 dom。而是一个 javascript 对象。

正常的 dom 节点在 html 中是这样表示:


<div class='testId'>
    <p>你好</p>
    <p>欢迎光临</p>
</div>

而在虚拟 dom 中大概是这样:


{
    tag: 'div',
    attributes:{
        class: ['testId']
    },
    children:[
        // p 元素
        // p 元素
    ]
}

我们可以将虚拟 dom 拆分成两部分进行理解:虚拟 + dom。

  • 虚拟: 表示虚拟 dom 不是真正意义上的 dom,而是一个 javascript 对象;
  • dom: 表示虚拟 dom 能以类似节点树的形式表示文档。

虚拟 dom 的作用

现在主流的框架都是声明式操作 dom 的框架。我们只需要描述状态与 dom 之间的映射关系即可,状态到视图(真实的 dom)的转换,框架会帮我们做。

最粗暴的做法是将状态渲染成视图,每次更新状态,都重新更新整个视图。

这种做法的性能可想而知。比较好的想法是:状态改变,只更新与状态相关的 dom 节点。虚拟 dom 只是实现这个想法的其中一种方法而已。

具体做法:

  • 状态 -> 真实 dom(最初)
  • 状态 -> 虚拟 dom -> 真实 dom(使用虚拟 dom)

状态改变,重新生成一份虚拟 dom,将上一份和这一份虚拟 dom 进行对比,找出需要更新的部分,更新真实 dom。

vue 中的虚拟 dom

真实的 dom 是由 节点(Node)组成,虚拟 dom 则是由虚拟节点(vNode)组成。

虚拟 dom 在 vue 中主要做两件事:

  • 提供与真实节点(Node)对应的虚拟节点(vNode)
  • 将新的虚拟节点与旧的虚拟节点进行对比,找出需要差异,然后更新视图

“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼 —— vue 官网

vNode

什么是 vNode

上文提到,vNode(虚拟节点)对应的是真实节点(Node)。

vNode 可以理解成节点描述对象。描述了如何创建真实的 dom 节点。

vue.js 中有一个 vNode 类。可以使用它创建不同类型的 vNode 实例,不同类型的 vNode 对应着不同类型的 dom 元素。代码如下:


export default class VNode {
   constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    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 (): Component | void {
    return this.componentInstance
  }
}

从代码不难看出 vNode 类创建的实例,本质上就是一个普通的 javascript 对象。

vNode 的类型

前面我们已经介绍通过 vNode 类可以创建不同类型的 vNode。而不同类型的 vNode 是由有效属性区分。例如 isComment = true 表示注释节点;isCloned = true 表示克隆节点等等。

vNode 类型有:注释节点、文本节点、克隆节点、元素节点、组件节点。

以下是注释节点、文本节点和克隆节点的代码:



export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  // 注释
  node.isComment = true
  return node
}

export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// 用于静态节点和插槽节点
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
// 克隆节点
export function cloneVNode (vnode: VNode): VNode {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    // #7975
    // clone children array to avoid mutating original in case of cloning
    // a child.
    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
}

克隆节点其实就是将现有节点的所有属性赋值到新节点中,最后用 cloned.isCloned = true 标记自身是克隆节点。

元素节点通常有以下 4 个属性:

  • tag:节点名称。例如 div、p
  • data:节点上的数据。例如 class、style
  • children:子节点
  • context:在组件内呈现

组件节点与元素节点类似,包含两个独有的属性:

  • componentOptions:组件节点的选项参数,例如propsData、listeners、children、tag
  • componentInstance:组件的实例

patch

前面已经介绍了虚拟 dom 在 vue 中做的第一件事:提供与真实节点(Node)对应的虚拟节点(vNode);接下来介绍第二件事:将新的虚拟节点与旧的虚拟节点进行对比,找出需要差异,然后更新视图。

第二件事在 vue 中的实现叫做 patch,即打补丁、修补的意思。通过对比新旧 vNode,找出差异,然后在现有 dom 的基础上进行修补,从而实现视图更新。

对比 vNode 找差异是手段,更新视图才是目的。

而更新视图无非就是新增节点、删除节点和更新节点。接下来我们逐一分析什么时候新增节点、在哪里新增;什么时候删除节点,删除哪个;什么时候更新节点,更新哪个;

注:当 vNode 与 oldVNode 不相同的时候,以 vNode 为准。

新增节点

一种情况是:vNode 存在而 oldVNode 不存在时,需要新增节点。最典型的是初次渲染,因为 odlVNode 是不存在的。

另一种情况是 vNode 与 oldVNode 完全不是同一个节点。这时就需要使用 vNode 生成真实的 dom 节点并插入到 oldVNode 指向的真实 dom 节点旁边。oldVNode 则是一个被废弃的节点。例如下面这种情况:


<div>
  <p v-if="type === 'A'">
    我是节点A
  </p>
  <span v-else-if="type === 'B'">
    我是与A完全不同的节点B
  </span>
</div>

当 type 由 A 变为 B,节点就会从 p 变成 span,由于 vNode 与 oldVNode 完全不是同一个节点,所以需要新增节点。

删除节点

当节点只在 oldVNode 中存在时,直接将其删除即可。

更新节点

前面介绍了新增节点和删除节点的场景,发现它们有一个共同点:vNode 与 oldVNode 完全不相同。

但更常见的场景是 vNode 与 oldVNode 是同一个节点。然后我们需要对它们(vNode 与 oldVNode)进行一个更细致的对比,再对 oldVNode 对应的真实节点进行更新。

对于文本节点,逻辑自然简单。首先对比新旧 vNode,发现是同一个节点,然后将 oldVNode 对应的 dom 节点的文本改成 vNode 中的文本即可。但对于复杂的 vNode,比如界面中的一颗树组件,这个过程就会变得复杂。

新增节点 - 源码分析

思考一下:前面说到 vNode 的类型有:注释节点、文本节点、克隆节点、元素节点、组件节点。请问这几种类型都会被创建并插入到 dom 中吗?

答:只有注释节点、文本节点、元素节点。因为 html 只认识这几种。

由于只有上面三种节点类型,根据类型做响应的创建,然后插入对应的位置即可。

以元素节点为例,如果 vNode 有 tag 属性,则说明是元素节点。则调用 createElement 方法创建对应的节点,接下来就通过 appendChild 方法插入到指定父节点中。如果父元素已经在视图中,那么把元素插入到它下面将会自动渲染出来;如果 vNode 的 isComment 属性是 true,则表示注释节点;都不是则是文本节点;

通常元素里面会有子节点,所以这里涉及一个递归的过程,也就是将 vNode 中的 children 依次遍历,创建节点,然后插入到父节点(父节点也就是刚刚创建出的 dom 节点)中,一层一层的递归进行。

请看源码:


// 创建元素
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // This vnode was used in a previous render!
    // now it's used as a new node, overwriting its elm would cause
    // potential patch errors down the road when it's used as an insertion
    // reference node. Instead, we clone the node on-demand before creating
    // associated DOM element for it.
    vnode = ownerArray[index] = cloneVNode(vnode);
  }

  vnode.isRootInsert = !nested; // for transition enter check
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }

  var data = vnode.data;
  var children = vnode.children;
  var tag = vnode.tag;
  // 有 tag 属性,表示是元素节点
  if (isDef(tag)) {
    vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns, tag)
      // 创建元素。nodeOps 涉及到跨平台
      : nodeOps.createElement(tag, vnode);
    setScope(vnode);

    
    {
      // 递归创建子节点,并将子节点插入到父节点上
      createChildren(vnode, children, insertedVnodeQueue);
      if (isDef(data)) {
        invokeCreateHooks(vnode, insertedVnodeQueue);
      }
      // 将 vnode 对应的元素插入到父元素中
      insert(parentElm, vnode.elm, refElm);
    }

  // isComment 属性表示注释节点
  } else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text);
    // 插入父节点
    insert(parentElm, vnode.elm, refElm);
  // 否则就是子节点
  } else {
    vnode.elm = nodeOps.createTextNode(vnode.text);
    // 插入父节点
    insert(parentElm, vnode.elm, refElm);
  }
}

// 递归创建子节点,并将子节点插入到父节点上。vnode 表示父节点
function createChildren (vnode, children, insertedVnodeQueue) {
  if (Array.isArray(children)) {
    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(children);
    }
    // 依次创建子节点,并将子节点插入到父节点中
    for (var i = 0; i < children.length; ++i) {
      createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
    }
  } else if (isPrimitive(vnode.text)) {
    nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
  }
}

删除节点 - 源码分析

删除节点非常简单。直接看源码:


// 删除一组指定节点
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  for (; startIdx <= endIdx; ++startIdx) {
    var ch = vnodes[startIdx];
    if (isDef(ch)) {
      if (isDef(ch.tag)) {
        removeAndInvokeRemoveHook(ch);
        invokeDestroyHook(ch);
      } else { // Text node
        // 删除个节点
        removeNode(ch.elm);
      }
    }
  }
}

// 删除单个节点
function removeNode (el) {
  var parent = nodeOps.parentNode(el);
  // element may have already been removed due to v-html / v-text
  if (isDef(parent)) {
    // nodeOps里封装了跨平台的方法
    nodeOps.removeChild(parent, el);
  }
}

以上就是vue 虚拟DOM快速入门的详细内容,更多关于vue 虚拟DOM的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

vue 虚拟DOM快速入门

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

创建虚拟环境:使用pipenv快速入门

快速上手pipenv:创建你的第一个虚拟环境在Python开发中,使用虚拟环境是一种常见的做法,它可以帮助我们隔离项目和依赖包之间的冲突,使得项目之间可以独立运行。而pipenv是一个Python包管理工具,它集成了虚拟环境和依赖包管理的
创建虚拟环境:使用pipenv快速入门
2024-01-16

Vue虚拟DOM详细介绍

虚拟DOM的概念是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点和上一次生成的虚拟节点进行对比,只渲染不同的部分
2022-11-13

如何快速进入虚拟主机

要快速进入虚拟主机,您可以按照以下步骤进行操作:1. 获取虚拟主机的登录信息:通常您会收到虚拟主机提供商发送给您的电子邮件,其中包含了虚拟主机的IP地址、用户名和密码。如果您没有收到这些信息,请联系虚拟主机提供商进行获取。2. 打开终端或命
2023-09-06

python 快速入门

导入#from dir1 import test#import dir1.test as test列表推到:b3 =[x for x in xing if x in ming]print(b3) li = [1, 2, 3, 4][elem
2023-01-31

Python3快速入门

Python3快速入门Python3快速入门(一)——Python简介https://blog.51cto.com/9291927/2385592Python3快速入门(二)——Python3基础https://blog.51cto.com
2023-01-31

JDBC快速入门

JDBC快速入门 详解1.0DriverManager功能1   功能22.0 connection对象 3.0 statement对象  4.0 ResultSet遍历结果集的一个案例 import java.sql.*;public class DQLte
JDBC快速入门
2020-11-18

vue快速入门基础知识教程

VUE是一套前端框架,免除了原生JavaScript中的DOM操作,简化书写。VUE基于MVVM(Model-View_ViewModel)思想,实现数据双向绑定。Vue的核心库只关注图层.响应式数据绑定和组件化开发是其两大特点。
2023-01-28

Vue虚拟Dom到真实Dom的转换方法

这篇文章主要介绍“Vue虚拟Dom到真实Dom的转换方法”,在日常操作中,相信很多人在Vue虚拟Dom到真实Dom的转换方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue虚拟Dom到真实Dom的转换方法
2023-06-20

Vue基础知识快速入门教程

这篇文章主要介绍了Vue基础知识快速入门教程,我们可以先学会用,使用一段时间之后,回头来熟悉一下Vue框架以及它的特点,需要的朋友可以参考下
2023-05-18

Oracle快速入门_day01

公司目前项目使用到了Oracle数据库 本人之前接触不多 计划4天 将Oracle数据库 快速入门 并结合开发使用,Oracle与MySql语法相似度很高,要一起学的小伙伴压力不要太大Oracle数据库简介Oracle Database,又名Oracle RD
Oracle快速入门_day01
2016-05-18

编程热搜

目录