Vue之虚拟DOM篇

116 阅读3分钟

VNode

VNode产生的前提是浏览器中的 DOM 是很“昂贵"的,为了更直观的感受,我们可以简单的把一个简单的 div 元素的属性都打印出来,如图所示:

img

可以看到,真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。当我们频繁的去做 DOM 更新,会产生一定的性能问题。

而 Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点(核心就是几个关键属性,标签名、数据、子节点、键值等),并且由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它比创建一个 DOM 的代价要小很多。

同时,由于它不包含具体的操作 DOM 的方法,因此 vnode 不依赖某个平台。于是配合不同的渲染工具,跨平台渲染成为可能。

另外,有了一层真实 DOM 的抽象以后:

  • 可以将虚拟节点缓存下来,然后使用新创建的虚拟节点和上一次渲染时的缓存的虚拟节点进行对比,然后根据对比结果进行最小代价的操作DOM更新。(也可以不进行diff直接采用全量更新DOM,但这么做的问题是如果组件只有一个节点发生变化,那么全量更新会造成很大的性能浪费)
  • 同时对 DOM 的多次操作结果一次性的更新到页面上(因为vue更新页面是异步的,所以多次对DOM的操作结果会push到一个nexttick队列中,然后一次性更新到页面中),减少频繁操作 DOM 的导致的重绘重排次数,提高性能。

但是,相应的缺点就是首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比直接 innerHTML 插入慢。

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
  }
​
  get child (): Component | void {
    return this.componentInstance
  }
}