Vue源码~虚拟Dom | 八月更文挑战

126 阅读1分钟

什么是虚拟 DOM?

  • 虚拟 DOM(Virtual DOM) 是使用 JavaScript 对象描述真实的 DOM
  • Vue.js 中的虚拟 DOM 借鉴 Snabbdom,并添加了 Vue.js 的特性。
    • eg: 指令和组件机制

为什么要使用虚拟 DOM

  • 避免直接操作 DOM,提高开发效率
  • 作为一个中间层可以跨平台
  • 虚拟 DOM 不一定可以提高性能
    • 首次渲染的时候会增加开销
    • 复杂视图情况下提升渲染性能

示例代码

const vm = new Vue({
      el: '#app',
      render (h) {
        // h(tag, data, children)
        // return h('h1', this.msg)
        // return h('h1', { domProps: { innerHTML: this.msg } })
        // return h('h1', { attrs: { id: 'title' } }, this.msg)
        const vnode = h(
          'h1', 
          { 
            attrs: { id: 'title' } 
          },
          this.msg
        )
        console.log(vnode)
        return null
      },
      data: {
        msg: 'Hello Vue'
      }
    })

h 函数

  • vm.$createElement(tag, data, children, normalizeChildren)
    • tag - 标签名称或者组件对象
    • data - 描述 tag,可以设置 DOM 的属性或者标签的属性
    • children - tag 中的文本内容或者子节点

VNode

  • VNode 的核心属性
    • tag
    • data
    • children
    • text
    • elm
    • key

VNode 的创建过程 - createElement

src/core/vdom/create-element.js

代码片段


const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

// wrapper function for providing a more flexible interface
// without getting yelled at by flow
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)
}

update

src/core/instance/lifecycle.js

// _update 方法的作用是把 VNode 渲染成真实的 DOM
  // 首次渲染会调用,数据更新会调用
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

patch 函数

src/platforms/web/runtime/index.js

import { patch } from './patch'
Vue.prototype.__patch__ == inBrowser ? patch : noop

src/platforms/web/runtime/patch.js

export const patch: Function = createPatchFunction({ nodeOps, modules });

执行过程

  • createElm() - 把 vnode 转换成真实 DOM 挂载到 DOM 树上
  • patchVnode()
// 创建 DOM 节点
createElm(
 vnode,
 insertedVnodeQueue,
 // extremely rare edge case: do not insert if old element is in a
 // leaving transition. Only happens when combining transition +
 // keep-alive + HOCs. (#4590)
 oldElm._leaveCb ? null : parentElm,
 nodeOps.nextSibling(oldElm) // 如果这个不为空,则会把创建好的dom插入到它之前
)
// 更新操作,diff 算法
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)

// updateChildren
// diff 算法
 // 更新新旧节点的子节点
 function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly)

整个Diff过程分析 点击查看