Vue源码学习(三):虚拟DOM

11 篇文章 0 订阅

Vue源码学习



一、什么是虚拟DOM?

  虚拟DOM是随着时代发展的产物。
  早期,页面交互效果比较简单,无复杂状态管理,无需频繁操作DOM,使用jQuery开发即可。
  随着需要越来越复杂,操作DOM越来越频繁。用jQuery开发,那会有相当多的代码操作DOM,状态也很难管理。
  现在,Vue.js、Angular和React都是声明式操作DOM。即通过描述状态(变量)和DOM直接的映射关系,就可以将状态渲染成视图。不用手动操作DOM。
  而当状态发生改变时,DOM要重新渲染,这时如果渲染整个DOM无疑是浪费了一些性能。这个问题有很多解决方案,虚拟DOM是其中一种。虚拟DOM的解决方式是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。渲染前,使用新生成的虚拟节点树和上一次生成的虚拟节点树对比,只渲染不同的部分。

  用一个JS对象描述一个DOM节点,这个JS对象叫做虚拟节点(Vnode)虚拟DOM就是由虚拟节点组成的。

<div class="a" id="b">我是内容</div>

{
  tag:'div',        // 元素标签
  attrs:{           // 属性
    class:'a',
    id:'b'
  },
  text:'我是内容',  // 文本内容
  children:[]       // 子元素
}

二、为什么要引入虚拟DOM

  Vue是数据驱动视图的,数据发生变化视图就要更新,更新视图的时候难免要操作DOM,而操作DOM是非常消耗性能的,因为浏览器标准把DOM设计的非常复杂:

let div = document.createElement('div')
let str = ''
for (const key in div) {
  str += key + ''
}
console.log(str)

在这里插入图片描述
  上图一个空div就打印这么多东西,由此,真实的DOM节点数据会占据更大的内存,频繁的做DOM更新,会产生一定的性能问题,因为DOM更新可能带来页面的重绘或重排。
  那Vue的解决方案是:用JS的计算性能换取操作DOM消耗的性能 。
  怎么计算呢?我们用JS对象模拟出了虚拟节点,数据变化时,生成新的虚拟节点,跟老的虚拟节点进行比对,比对有个算法叫做DOM-Diff算(后续介绍),比对出需要更新的地方,然后去更新视图。
  这就是虚拟DOM产生的原因和用途。此外,使用虚拟DOM也能使得Vue不再依赖浏览器环境,可以很容易在Broswer端或服务端操作虚拟DOM,需要 render 时再将虚拟 DOM 转换为真实 DOM 即可。这也使得 Vue 有了实现服务器端渲染的能力。

三、Vue中的虚拟DOM

1、关于VNode

  虚拟DOM就是用JS对象来描述真实节点,这个JS对象我们叫做虚拟节点(Vnode),那VNode是怎么生成的呢?Vue中有一个VNode类,使用它可以实例化不同类型的VNode实例,不同类型的实例表示不同类型的DOM元素。
  例如,DOM元素有元素节点、文本节点和注释节点等,vnode实例也有对应的元素节点、文本节点和注释节点等。VNode类代码如下:

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

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        /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.children = children  /*当前节点的子节点,是一个数组*/
    this.text = text     /*当前节点的文本*/
    this.elm = elm       /*当前虚拟节点对应的真实dom节点*/
    this.ns = undefined            /*当前节点的名字空间*/
    this.context = context          /*当前组件节点对应的Vue实例*/
    this.fnContext = undefined       /*函数式组件对应的Vue实例*/
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key           /*节点的key属性,被当作节点的标志,用以优化*/
    this.componentOptions = componentOptions   /*组件的option选项*/
    this.componentInstance = undefined       /*当前节点对应的组件的实例*/
    this.parent = undefined           /*当前节点的父节点*/
    this.raw = false         /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.isStatic = false         /*静态节点标志*/
    this.isRootInsert = true      /*是否作为跟节点插入*/
    this.isComment = false             /*是否为注释节点*/
    this.isCloned = false           /*是否为克隆节点*/
    this.isOnce = false                /*是否有v-once指令*/
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  get child (): Component | void {
    return this.componentInstance
  }
}

  从上面的代码可看出,vnode本质上是JS中一个普通对象,是从VNode类中实例化的对象。
  简单说,vnode可以理解为节点描述对象,描述了怎么取创建真实的DOM节点。如tag表示一个元素节点的名称,text表示一个文本节点的文本,children表示子节点等。

2、VNode作用

  由于每次渲染视图都先创建vnode,然后使用它创建真实DOM插入到页面中,所以将上一次渲染视图时所创建的vnode缓存起来,之后每当需要重新渲染视图时,将新创建的vnode和上一次缓存的vnode进行比对,查看它们之间不一样的地方,找出这些不一样的然后基于此去修改真实的DOM(DOM-DIFF算法)。这样就不用把整个DOM进行替换,可以减少对DOM的操作。

  Vue.js目前对状态的侦测策略采用中等粒度,当状态发生变化时,只通知到组件级别,然后组件内使用虚拟DOM来渲染视图。

3、VNode的类型

注释节点

export const createEmptyVnode(text) {
	const node = new Vnode();
	node.text = text;
	node.isComment = true;
	return node;
}

可以看出,一个注释节点只有两个有效属性——text和isComment,其余属性全是默认的undefined或者false。
例如,一个真实的注释节点: <!-- 注释节点 -->,所对应的vnode是这样的:

{
	text: "注释节点",
	isComment: true
}

文本节点

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

通过代码可知,当文本属性的vnode被创建时,它只有一个text属性:

{
	text: 'Hello Lisa'
}

克隆节点

  克隆节点的作用是优化静态节点和插槽节点。
  以静态节点为例,当组件内的某个状态发生变化后,当前组件会通过虚拟DOM重新渲染视图,静态节点因为内容不变,所以除了首次渲染需要执行渲染函数获取vnode外,后续更新不需要执行渲染函数重新生成vnode。因此,就会用创建克隆节点的方法将vnode克隆一份,使用克隆节点渲染。这样就不用重新执行渲染函数生成新的静态节点的vnode,从而提升一定程度的性能。

// 创建克隆节点
export function cloneVNode (vnode, deep) {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children,
    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
  if (deep && vnode.children) {
  	cloned.children = cloneVNodes(vnode.children)
  }
  cloned.isCloned = true
  return cloned
}

  从代码可以看出,克隆节点就是把已有节点的属性全部复制到新节点中,而现有节点和新克隆得到的节点之间唯一的不同就是克隆得到的节点isCloned为true。

元素节点

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

  • tag:一个节点名称,如p、ul、li、div等
  • data:该属性包含了一些节点上的数据,如attrs、class、style等
  • children:当前节点的子节点列表
  • context:当前组件的Vue.js实例
export function createElementVNode(vm, tag, data, ...children) {
    if (data == null) {
        data = {}
    }
    let key = data.key;
    if (key) {
        delete data.kye
    }
    return new VNode(vm,tag, key, data, children)
}

例如:

// 真实DOM节点
<div id='app'><span>{{name}}</span></div>

// VNode节点
{
  tag:'div',
  data:{id:'app'},
  text: undefined,
  children:[
    {
      tag:'span',
      text:undefined
    }
  ]
}

组件节点

组件节点除了有元素节点具有的属性之外,它还有两个特有的属性:

  • componentOptions :组件的option选项,如组件的props等
  • componentInstance :当前组件节点对应的Vue实例

函数式组件节点

函数式组件节点相较于组件节点,它又有两个特有的属性:

  • fnContext:函数式组件对应的Vue实例
  • fnOptions: 组件的option选项

总结

  以上写了什么是虚拟DOM、虚拟DOM作用以及VNode的作用和种类。

  虚拟DOM运作原理是使用状态生成虚拟节点,然后用虚拟节点渲染视图。

  之所以先用状态生成虚拟节点,是因为如果直接用状态生成真实DOM,会有一定程序的性能浪费。而先创建虚拟节点,就可以将它缓存起来,然后用新创建的虚拟节点和上一次渲染时缓存的虚拟节点进行比对,根据比对结果只更新变化的部分,从而避免不必要的DOM操作,节省一定性能开销。

  Vue.js通过模板描述状态和视图之间的映射关系,所以它会先将模板编译成渲染函数,然后执行渲染函数生成虚拟节点,最后使用虚拟节点更新视图。

  因此,虚拟DOM在Vue.js做的事情就是:提供虚拟节点vnode、对新旧两个vnode进行比对,并根据结果进行DOM操作来更新视图。

建议下载源码阅读,便于理解…

参考书籍:《深入浅出Vue.js》
手写源码:vue2源码解析
官网源码:官网网址
视频链接:B站视频
源码入口:源码入口

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值