随笔录--虚拟 DOM

本文介绍了虚拟DOM的概念,它是如何通过JavaScript对象抽象DOM,以及在提高性能和开发效率方面的应用。重点讨论了虚拟DOM在大规模动态页面中的优势,以及与真实DOM操作的区别,包括Diff算法的作用。
摘要由CSDN通过智能技术生成

官方解析

虚拟 DOM(Virtual DOM)是一种将浏览器 DOM 抽象为 JavaScript 对象的技术,用于提高 DOM 操作的效率和性能。虚拟 DOM 可以在渲染前对组件的变化进行计算,减少 DOM 操作的次数,从而提高渲染性能。

使用虚拟 DOM 可以提高性能,但并不是一定更快。虚拟 DOM 需要进行额外的计算和比较操作,而这些操作也会消耗一定的时间和性能。因此,虚拟 DOM 适用于大规模、高度动态的页面,而在简单的静态页面中使用虚拟 DOM 并不能提高性能。此外,虚拟 DOM 还可以提高开发效率,使代码更易于维护和调试。

虚拟DOM是一个 js对象,一个什么样的对象呢?一个用来表示真是DOM的对象,要记住这句话,举个例子,请看一下真实DOM:

<ul id="list">
  <li class="item">哈哈</li>
  <li class="item">嘿嘿</li>
  <li class="item">呵呵</li>
</ul>

对应的虚拟DOM为:

let oldVDOM = { // 旧虚拟DOM
  tagName: 'ul', // 标签名
  props: { // 标签属性
    id: 'list'
  },
  children: [ // 标签子节点
    {
      tagName: 'li', props: { class: 'item' }, children: [ '哈哈' ]
    },
    {
      tagName: 'li', props: { class: 'item' }, children: [ '嘿嘿' ]
    },
    {
      tagName: 'li', props: { class: 'item' }, children: [ '呵呵' ]
    },
  ]
}

这个时候,修改一个li标签的文本:

<ul id="list">
  <li class="item">哈哈</li>
  <li class="item">嘿嘿</li>
  <li class="item">快来学习Diff算法了,呵呵</li>
</ul>

这个时候生成的新虚拟DOM为:

let newVDOM = { // 新虚拟DOM
  tagName: 'ul', // 标签名
  props: { // 标签属性
    id: 'list'
  },
  children: [ // 标签子节点
    {
      tagName: 'li', props: { class: 'item' }, children: [ '哈哈' ]
    },
    {
      tagName: 'li', props: { class: 'item' }, children: [ '嘿嘿' ]
    },
    {
      tagName: 'li', props: { class: 'item' }, children: [ '快来学习Diff算法了,呵呵' ]
    },
  ]
}

这就是我们平常说的新旧两个虚拟DOM,这个时候的新虚拟DOM是数据的最新状态,那么我们直接拿新虚拟DOM去渲染成真实DOM的话,效率真的要比直接操作真实DOM高吗?那肯定是不会的,如图所示:

图片

从图中一看便知,肯定是第2种方式比较快,因为第1种方式中间还夹着一个虚拟DOM的步骤,所以虚拟DOM比真实DOM快 这句话是错的,或者说不严谨。正确的说法应该是:虚拟DOM算法操作真实DOM,性能高于直接操作DOM,虚拟DOM和虚拟DOM算法是两种概念。虚拟DOM算法 = 虚拟DOM + Diff算法

因为操作DOM会引起页面的回流或者重绘,而这两个东西是非常耗费性能的。因此我们越少操作真实dom就越好。相比起来,通过多一些预先计算来减少DOM的操作要划算的多。

虚拟DOM是通过JS对象模拟出来的DOM节点,domDiff是通过特定算法计算出来一次操作所带来的DOM变化。减少 DOM 操作的次数,从而提高渲染性能。可以提高开发效率

但是,“使用虚拟DOM会更快”这句话并不一定适用于所有场景。例如:一个页面就有一个按钮,点击一下,数字加一,那肯定是直接操作DOM更快。虚拟 DOM 需要进行额外的计算和比较操作,而这些操作也会消耗一定的时间和性能。即使是复杂情况,浏览器也会对我们的DOM操作进行优化,大部分浏览器会根据我们操作的时间和次数进行批量处理,所以直接操作DOM也未必很慢。

一、什么是虚拟DOM

实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上

在Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别

创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

vue中虚拟DOM技术 定义真实DOM

<div id="app">
    <p class="p">节点内容</p>
    <h3>{{ foo }}</h3>
</div>

实例化vue

const app = new Vue({
    el:"#app",
    data:{
        foo:"foo"
    }
})

观察render,得到虚拟DOM

(function anonymous(
) {
 with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',{staticClass:"p"},
       [_v("节点内容")]),_v(" "),_c('h3',[_v(_s(foo))])])}})

通过VNode,vue可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能

二、使用虚拟DOM的原因

DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的

真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下:

图片

由此可见,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验

举个例子:

你用传统的原生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程

当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程

而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算

很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI

三、如何实现虚拟DOM 首先可以看看vue中VNode的结构

源码位置:src/core/vdom/vnode.js

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next https://github.com/answershuto/learnVue*/
  get child (): Component | void {
    return this.componentInstance
  }
}

这里对 VNode 进行稍微的说明:

所有对象的 context 选项都指向了 Vue 实例 elm 属性则指向了其相对应的真实 DOM 节点 vue是通过createElement生成VNode

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值