vue2-虚拟DOM

vue2-虚拟DOM

1. 什么是虚拟DOM

  • 要说虚拟DOM还是得先说真是DOM,真实DOM大家应该都很了解,就是浏览器最后渲染的各种元素节点,比如DIV,P等
  • 啥是虚拟DOM呢,简单点来说就是用JS对象,模拟真实DOM,形成一一对应的关系。以JS对象作为基础的树,用对象的属性来描述节点,最终通过一些列操作把这颗虚拟树映射到真是DOM上。
  • 那肯定要问了,既然虚拟DOM最后要映射到真是DOM上,为啥要多此一举呢,先概括一句话,优化页面的性能,怎么优化的呢,且看后续
  • 一个虚拟DOM至少包含三个属性
  1. 标签名tag
  2. 属性attrs
  3. 子元素对象children

节点内容

{{foo}}

  • 通过render的render,我们可以看到虚拟DOM
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',{staticClass:"p"}
,
 [_v("节点内容")]),_v(" "),_c('h3',[_v(_s(foo))])])}})

2. 为什么需要虚拟DOM

  • 真实DOM的每一个元素都包含着大量的属性,频繁地区操作DOM会导致很严重的性能问题,页面甚至会出现卡顿等现象。比如我们用原生JS一次操作10个DOM节点时,浏览器在收到第一个请求后,就会马上从构建DOM树开始从头到尾执行一遍流程,而不会等后面的9次更新都完成再开始执行流程。而通过VNode,虚拟DOM不会立即操作DOM,而是将10从更新的diif内容保存到一个js对象中,最终将这个js对象一次性attach到DOM树上,避免了大量的无畏计算
  • 虚拟DOM的优势不仅是diff算法,他还抽象原生的渲染过程,实现了跨平台的能力,不局限于浏览器的DOM,可以是安卓或者IOS的原生组件

3. vue如何实现的虚拟DOM

  • 我们可以查看vue中的VNode结构,在源码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; // 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类型,可以参考VNod
        eData类型中的数据信息*/
         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
     }
         // DEPRECATED: alias for componentInstance for backwards compat.
         /* istanbul ignore next https://github.com/answershuto/learnVue*/
         get child (): Component | void {
         return this.componentInstance
     }
}
  • 所有对象的context选项都指向了Vue实例,elm属性则指向了其真实的DOM节点
  • vue是通过createElement生成VNode
  • 源码位置: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)
}
  • 这里createElement本质是对_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.string
    ify(data)}\n` +
     'Always create fresh vnode data objects in each render!',
     context`
     )
     return createEmptyVNode()
     }
     // object syntax in v-bind
     if (isDef(data) && isDef(data.is)) {
     tag = data.is
     }
     if (!tag) {
     // in case of component :is set to falsy value
     return createEmptyVNode()
     }
     ...
     // support single function children as default scoped slot
     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)
     }
    // 创建VNode
     ...
}
  • _createElement接收5个参数
  1. context,表示VNode上下文环境,是Component类型
  2. tag,标识标签,可以是一个字符串,也可以是一个Component,因素元素既可以是标签也可以是子元素么,这个很好理解
  3. data,表示VNode的数据,是一个VNodeData类型
  4. normalizationType,表示子节点规范的类型,因为render可能是编译生成的也有可能是用户自己手写的,类型不同规范的方法也就不一样
if (normalizationType === ALWAYS_NORMALIZE) {
 children = normalizeChildren(children)
} else if ( === SIMPLE_NORMALIZE) {
 children = simpleNormalizeChildren(children)
}
  • 无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范,使得children变成一个类型为VNode的Array

你可能感兴趣的:(VUE2,vue.js,javascript,前端,虚拟DOM,virtual,DOM,VNode)