博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Vue源码学习】Virtual Dom 原理解析
阅读量:6857 次
发布时间:2019-06-26

本文共 3187 字,大约阅读时间需要 10 分钟。

一、概述

我们都知道频繁操作Dom是一个非常耗费性能的事情,这是因为浏览器的Dom规范设计的非常复杂,

我们可以看到,一个简单的div元素,就有这么多复杂的属性,足以见得如果频繁操作Dom,将有多么耗费性能。在这种情况下,Virtual Dom应运而生,解决了频繁操作Dom的性能开销问题。它是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。

二、VNode

1、VNode属性

Virtual Dom把真实一个个节点转换成虚拟节点(VNode),用来表示Dom中的树形结构,一个VNode包含以下属性:

上图是Vue中对于VNode的定义,我们挑其中的一些主要属性:

  • tag: 当前节点的标签名

  • data: 当前节点的数据对象,具体包含哪些字段可以参考vue源码types/vnode.d.ts中对VNodeData的定义

  • children: 数组类型,包含了当前节点的子节点

  • text: 当前节点的文本,一般文本节点或注释节点会有该属性

  • elm: 当前虚拟节点对应的真实的dom节点

  • ns: 节点的namespace

  • context: 编译作用域

  • functionalContext: 函数化组件的作用域

  • key: 节点的key属性,用于作为节点的标识,有利于patch的优化

  • componentOptions: 创建组件实例时会用到的选项信息

  • child: 当前节点对应的组件实例

  • parent: 组件的占位节点

  • raw: raw html

  • isStatic: 静态节点的标识

  • isRootInsert: 是否作为根节点插入,被<transition>包裹的节点,该属性的值为false

  • isComment: 当前节点是否是注释节点

  • isCloned: 当前节点是否为克隆节点

  • isOnce: 当前节点是否有v-once指令

二、VNode 分类

VNode可以理解为vue框架的虚拟dom的基类,通过new实例化的VNode大致可以分为几类

  • EmptyVNode: 没有内容的注释节点
  • TextVNode: 文本节点
  • ElementVNode: 普通元素节点
  • ComponentVNode: 组件节点
  • CloneVNode: 克隆节点,可以是以上任意类型的节点,唯一的区别在于isCloned属性为true

三、createElement()

Vue中使用createElement()方法来生成VNode。代码由于过长就不在此贴出了,源代码在 下。另外从网上偷了张图来描述下这个方法执行的过程:

三、patch

patch是另一个Virtual Dom的核心,它用来比对前后两次改动的Dom的异同,并将改动的部分更新至真实Dom,比对的策略应用了Diff算法。

Vue中把这块分为三部分:patch、patchVNode、updateChildren,我们先来看patch部分:

patch的策略是

  1. 如果vnode不存在但是oldVnode存在,说明意图是要销毁老节点,那么就调用invokeDestroyHook(oldVnode)来进行销毁

  2. 如果oldVnode不存在但是vnode存在,说明意图是要创建新节点,那么就调用createElm来创建新节点

  3. vnodeoldVnode都存在时

    • 如果oldVnodevnode是同一个节点,就调用patchVnode来进行patch (diff算法)

    • vnodeoldVnode不是同一个节点时,如果oldVnode是真实dom节点或hydrating设置为true,需要用hydrate函数将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点,根据vnode创建一个真实dom节点并插入到该父节点中oldVnode.elm的位置

patchVnode

  1. 如果oldVnodevnode完全一致,那么不需要做任何事情

  2. 如果oldVnodevnode都是静态节点,且具有相同的key,当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elmoldVnode.child都复制到vnode上,也不用再有其他操作

  3. 如果vnode不是文本节点或注释节点,调用updateChildren执行更新子节点的操作

  4. 如果vnode是文本节点或注释节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以

updateChildren

  • 如果oldVnodevnode都有子节点,且2方的子节点不完全一致,就执行更新子节点的操作,算法如下
    • 分别获取oldVnodevnodefirstChildlastChild,赋值给oldStartVnodeoldEndVnodenewStartVnodenewEndVnode
    • 如果oldStartVnodenewStartVnode是同一节点,调用patchVnode进行patch,然后将oldStartVnodenewStartVnode都设置为下一个子节点,重复上述流程
    • 如果oldEndVnodenewEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnodenewEndVnode都设置为上一个子节点,重复上述流程
    • 如果oldStartVnodenewEndVnode是同一节点,调用patchVnode进行patch,如果removeOnlyfalse,那么可以把oldStartVnode.elm移动到oldEndVnode.elm之后,然后把oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,重复上述流程
    • 如果newStartVnodeoldEndVnode是同一节点,调用patchVnode进行patch,如果removeOnlyfalse,那么可以把oldEndVnode.elm移动到oldStartVnode.elm之前,然后把newStartVnode设置为下一个节点,oldEndVnode设置为上一个节点,重复上述流程
    • 如果以上都不匹配,就尝试在oldChildren中寻找跟newStartVnode具有相同key的节点,如果找不到相同key的节点,说明newStartVnode是一个新节点,就创建一个,然后把newStartVnode设置为下一个节点
    • 如果上一步找到了跟newStartVnode相同key的节点,那么通过其他属性的比较来判断这2个节点是否是同一个节点,如果是,就调用patchVnode进行patch,如果removeOnlyfalse,就把newStartVnode.elm插入到oldStartVnode.elm之前,把newStartVnode设置为下一个节点,重复上述流程
    • 如果在oldChildren中没有寻找到newStartVnode的同一节点,那就创建一个新节点,把newStartVnode设置为下一个节点,重复上述流程
    • 如果oldStartVnodeoldEndVnode重合了,并且newStartVnodenewEndVnode也重合了,这个循环就结束了
  • 如果只有oldVnode有子节点,那就把这些节点都删除
  • 如果只有vnode有子节点,那就创建这些子节点
  • 如果oldVnodevnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

转载于:https://juejin.im/post/5c17b1daf265da6125780606

你可能感兴趣的文章
gitlab提示“Your account is locked”
查看>>
性能调优之sar
查看>>
apache 日志中记录代理IP以及真实客户端IP
查看>>
寻找数组中第二大的数_代码实现
查看>>
MySQL5.7-winx64的zip包安装过程
查看>>
windows 7安装时提示gpt分区无法安装windows系统
查看>>
GPT分区不能安装Linux/Redhat 5.x的解决方法
查看>>
我的友情链接
查看>>
运行WordCount报错:java.lang.OutOfMemoryError: Java heap space
查看>>
2条ce1链路捆绑成一条4M的链路,做负载均衡
查看>>
我的友情链接
查看>>
Hadoop序列化与Java序列化
查看>>
三款主流云笔记软件比较
查看>>
梯形的面积可以通过几何画板来计算吗
查看>>
对于异步的理解
查看>>
JQuer的简单应用
查看>>
国产开源敏捷工具-fKanban
查看>>
01:数据结构和算法
查看>>
《人月神话》读后感1
查看>>
华为实习日记——第三十一天
查看>>