Vue2源码,虚拟DOM篇-VNode

643 阅读3分钟

什么是虚拟DOM

其实就是用一个JS对象来描述一个DOM节点

为什么要有虚拟DOM

用JS 的计算性能来换取DOM操作性能的消耗

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

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

Vue中的虚拟DOM

VNode类

通过这个类,可以虚拟化出不同类型的DOM节点

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
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  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?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    // 标签名
    this.tag = tag 
    // 当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,
    this.data = data
    /* 当前节点的子节点,是一个数组 */
    this.children = children
    /* 当前节点的文本 */
    this.text = text
    /* 当前节点对应的真实DOM节点 */
    this.elm = elm
    /* 当前节点的名字空间 */
    this.ns = undefined
    /* 当前组件节点对应的Vue实例 */
    this.context = context
    /* 函数式组件对应的Vue实例 */
    this.fnContext = undefined
    /*  */
    this.fnOptions = undefined
    this.fnScopeId = undefined
    /* 节点的key属性 */
    this.key = data && data.key
    /* 组件的options选项 */
    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
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

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

VNode 类型

/* 
  这是一个注释节点
  只需要两个属性
  text表示具体的注释信息
  isComment 是一个标识
 */
export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

/* 文本节点,只需要text属性,比较简单 */
export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

VNode作用

我们在视图渲染之前,把写好的template模板先编译成VNode并缓存下来,等到数据发生变化页面需要重新渲染的时候,我们把数据发生变化后生成的VNode与前一次缓存下来的VNode进行对比,找出差异,然后有差异的VNode对应的真实DOM节点就是需要重新渲染的节点,最后根据有差异的VNode创建出真实的DOM节点再插入到视图中,最终完成一次视图更新