Vue中的虚拟dom长什么样的

什么是虚拟dom

所谓虚拟DOM,就是用一个JS对象来描述一个DOM节点,像如下示例

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

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

为什么 有虚拟dom

先打印一个div(巨大):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6fAxfGD-1583846587920)(G:\aboutFrontEnd\学习笔记\图片\打印一个div.png)]
Vue是数据驱动视图的,数据发生变化视图就要随之更新,在更新视图的时候难免要操作DOM,而操作真实DOM又是非常耗费性能的。

那么有没有什么解决方案呢?当然是有的。我们可以JS的计算性能来换取操作DOM所消耗的性能。

最直观的思路就是我们不要盲目的去更新视图,而是通过对比数据变化前后的状态,计算出视图中哪些地方需要更新,只更新需要更新的地方,而不需要更新的地方则不需关心,这样我们就可以尽可能少的操作DOM了。这也就是上面所说的用JS的计算性能来换取操作DOM的性能。

我们可以用JS模拟出一个DOM节点,称之为虚拟DOM节点。当数据发生变化时,我们对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图。

这就是虚拟DOM产生的原因以及最大的用途。

我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗:

(1)innerHTML: render html string O(template size) + 重新创建所有 DOM 元素 O(DOM size)

(2)Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。

既然我们逃不掉操作DOM这道坎,但是我们可以尽可能少的操作DOM。那如何在更新视图的时候尽可能少的操作DOM呢?

Vue中的虚拟dom

Vue中是通过VNode类来实例化出不同类型的虚拟DOM节点,不同类型节点生成的属性的不同,所谓不同类型的节点其本质还是一样的,都是VNode类的实例,只是在实例化时传入的属性参数不同罢了。

源码:

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的作用

有了数据变化前后的VNode,我们才能进行后续的DOM-Diff找出差异,最终做到只更新有差异的视图,从而达到尽可能少的操作真实DOM的目的,以节省性能 。

VNode结点类型

  • 注释节点
  • 文本节点
  • 元素节点
  • 组件节点
  • 函数式组件节点
  • 克隆节点

js可以模拟dom,返之也可以渲染dom
模拟:

export default class Element {
 /**
 * @param {String} tag 'div'
 * @param {Object} props { class: 'item' }
 * @param {Array} children [ Element1, 'text']
 * @param {String} key option
 */
 constructor(tag, props, children, key) {
     this.tag = tag
     this.props = props
     if (Array.isArray(children)) {
     	this.children = children
     } else if (isString(children)) {
     	this.key = children
     	this.children = null
     }
     if (key) this.key = key
 }

渲染:

 render() {
     let root = this._createElement(
     this.tag,
     this.props,
     this.children,
     this.key
 )
 document.body.appendChild(root)
	 return root
 }
 create() {
 	return this._createElement(this.tag, this.props, this.children, this.key)
 }
 
 // 创建节点
 _createElement(tag, props, child, key) {
     // 通过 tag 创建节点
     let el = document.createElement(tag)
     // 设置节点属性
     for (const key in props) {
     if (props.hasOwnProperty(key)) {
         const value = props[key]
         el.setAttribute(key, value)
     }
 	}
     if (key) {
     el.setAttribute('key', key)
     }
 	// 递归添加子节点
     if (child) {
     child.forEach(element => {
         let child
         if (element instanceof Element) {
         child = this._createElement(
         element.tag,
         element.props,
         element.children,
         element.key
     )} else {

     	child = document.createTextNode(element)
     }
    	 el.appendChild(child)
     })
     }
     	return el
     }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值