| 什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路
 一、什么是虚拟DOM1、虚拟 DOM (Virtual DOM )这个概念相信大家都不陌生,从React到Vue,虚拟DOM为这两个框架都带来了跨平台的能力(React-Native和Weex) 2、实际上它只是一层对真实DOM的抽象,以JavaScript对象 (VNode节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上 3、在Javascript对象中,虚拟DOM表现为一个Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别 4、创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应 5、在vue中同样使用到了虚拟DOM技术 6、定义真实DOM 	<div id="app">
    	<p class="p">节点内容</p>
    	<h3>{{ foo }}</h3>
	</div>
 7、实例化vue 	const app = new Vue({
    	el:"#app",
    	data:{
        	foo:"foo"
    	}
	})
 8、观察render的render,我们能得到虚拟DOM 	(function anonymous(
	) {
		with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',{staticClass:"p"},
					  [_v("节点内容")]),_v(" "),_c('h3',[_v(_s(foo))])])}})
 9、通过VNode,vue可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能 二、为什么需要虚拟DOM1、DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的 2、真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下:
  3、由此可见,操作
 DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验 举个例子1、你用传统的原生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程 2、当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程 3、而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算  
 很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI 三、如何实现虚拟DOM1、首先可以看看vue中VNode的结构 2、源码位置: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; 
  functionalContext: Component | void; 
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; 
  parent: VNode | void; 
  raw: boolean; 
  isStatic: boolean; 
  isRootInsert: boolean; 
  isComment: boolean; 
  isCloned: boolean; 
  isOnce: boolean; 
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    
    this.tag = tag
    
    this.data = data
    
    this.children = children
    
    this.text = text
    
    this.elm = elm
    
    this.ns = undefined
    
    this.context = context
    
    this.functionalContext = undefined
    
    this.key = data && data.key
    
    this.componentOptions = componentOptions
    
    this.componentInstance = undefined
    
    this.parent = undefined
    
    this.raw = false
    
    this.isStatic = false
    
    this.isRootInsert = true
    
    this.isComment = false
    
    this.isCloned = false
    
    this.isOnce = false
  }
  
  
  get child (): Component | void {
    return this.componentInstance
  }
}
 3、这里对VNode进行稍微的说明:  
 (1)所有对象的 context选项都指向了Vue实例(2)elm属性则指向了其相对应的真实DOM节点
 4、vue是通过createElement生成VNode 5、源码位置:src/core/vdom/create-element.js export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}
 6、上面可以看到createElement方法实际上是对_createElement方法的封装,对参数的传入进行了判断 export function _createElement(
    context: Component,
    tag?: string | Class<Component> | Function | Object,
    data?: VNodeData,
    children?: any,
    normalizationType?: number
): VNode | Array<VNode> {
    if (isDef(data) && isDef((data: any).__ob__)) {
        process.env.NODE_ENV !== 'production' && warn(
            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
            'Always create fresh vnode data objects in each render!',
            context`
        )
        return createEmptyVNode()
    }
    
    if (isDef(data) && isDef(data.is)) {
        tag = data.is
    }
    if (!tag) {
        
        return createEmptyVNode()
    }
    ... 
    
    if (Array.isArray(children) &&
        typeof children[0] === 'function'
    ) {
        data = data || {}
        data.scopedSlots = { default: children[0] }
        children.length = 0
    }
    if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
    } else if ( === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
    }
	
    ...
}
 7、可以看到_createElement接收5个参数:  
 (1)context表示VNode的上下文环境,是Component类型(2)tag表示标签,它可以是一个字符串,也可以是一个Component(3)data表示VNode的数据,它是一个VNodeData类型(4)children表示当前VNode的子节点,它是任意类型的(5)normalizationType表示子节点规范的类型,类型不同规范的方法也就不一样,主要是参考render函数是编译生成的还是用户手写的
 8、根据normalizationType的类型,children会有不同的定义 if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
} else if ( === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
}
 9、simpleNormalizeChildren方法调用场景是render函数是编译生成的 10、normalizeChildren方法调用场景分为下面两种:> (1)render函数是用户手写的 
  (2)编译 slot、v-for的时候会产生嵌套数组
 11、无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范(使children变成了一个类型为VNode的Array),这里就不展开说了 12、规范化children的源码位置在:src/core/vdom/helpers/normalzie-children.js 13、在规范化children后,就去创建VNode let vnode, ns
if (typeof tag === 'string') {
  let Ctor
  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  if (config.isReservedTag(tag)) {
    
    vnode = new VNode(
      config.parsePlatformTagName(tag), data, children,
      undefined, undefined, context
    )
  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
    
    
    vnode = createComponent(Ctor, data, context, children, tag)
  } else {
    vnode = new VNode(
      tag, data, children,
      undefined, undefined, context
    )
  }
} else {
  
  vnode = createComponent(tag, data, context, children)
}
 14、createComponent同样是创建VNode 15、源码位置:src/core/vdom/create-component.js export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
 
  const baseCtor = context.$options._base
  
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  
  
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  data = data || {}
  
  
  resolveConstructorOptions(Ctor)
  
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  
  
  const listeners = data.on
  
  
  data.on = data.nativeOn
  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  
  installComponentHooks(data)
  
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }
  return vnode
}
 16、稍微提下createComponent生成VNode的三个关键流程:  
 (1)构造子类构造函数Ctor(2)installComponentHooks安装组件钩子函数(3)实例化 vnode
 小结1、createElement创建VNode的过程,每个VNode有children,children每个元素也是一个VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树结构 参考文献 
 (1)https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/create-element.html#children-%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96(2)https://juejin.cn/post/6876711874050818061
 |