Vue虚拟DOM大探秘

讲到虚拟Dom,那么我们先从初始化渲染开始。
src/core/instance/render.js

初始化相关属性

// 定义并导出initRender方法,参数为vm(组件)
export function initRender (vm: Component) {
	// 初始化根实例的虚拟节点
  vm._vnode = null // the root of the child tree
  // 定义实例的静态树节点
  vm._staticTrees = null // v-once cached trees
  // 获取vm的$options配置选项,并赋值给options
  const options = vm.$options
  //设置父占位符节点
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  // 设置renderContext存储父节点有无上下文
  const renderContext = parentVnode && parentVnode.context
  // 将子虚拟节点转换成格式化的对象结构存储在实例的$slot中
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  //初始化$scopedSlots并设置为空对象
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  // 绑定createElement函数到这个实例
  // 内部实际调用createElement函数,并且适当的渲染上下文
  // 参照的顺序是:tag, data, children, normalizationType, alwaysNormalize
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  // 规范化始终应用于公共版本,用于用户编写的渲染函数。
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  // attrs和$listeners被暴露了,为了更容易创建高阶组件
  // 他们需要做响应化这样高阶组件就能够保持实时的更新
  const parentData = parentVnode && parentVnode.data
	// 对事件和属性监听器进行响应式处理,建立观察状态
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

上面的方法主要就是初始化相关的属性,对事件和属性做响应化处理

挂载方法初始化返回虚拟节点
主要是给Vue原型对象挂载$nextTick和_render方法,render方法里面主要是调用createElement方法

// 定义并导出renderMixin
export function renderMixin (Vue: Class<Component>) {
// 为Vue原型对象绑定运行时相关的辅助方法
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)
// 挂载Vue原型对象$nextTick,参数为函数
  Vue.prototype.$nextTick = function (fn: Function) {
  // 返回xtTick函数的执行结果
    return nextTick(fn, this)
  }
// 挂载Vue原型对象_render
  Vue.prototype._render = function (): VNode {
  //将实例赋值给vm变量
    const vm: Component = this
     // 从$options对象中获取render方法和 _parentVnode对象
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    // 设置父节点虚拟节点,允许渲染函数具有访问权限到占位符节点上的数据
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      // 不需要维护堆栈,因为所有的render函数都是被分开的。当父组件被打补丁时,被叫做嵌套组件render函数。
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      // 返回错误的渲染结果,或者以前的虚拟节点去阻止渲染错误造成空组件
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    // 如果返回的数组只包含一个节点,允许他这么操作
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    // 如果呈现渲染函数出错,就返回一个空节点
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    // 返回虚拟节点
    return vnode
  }
}

我们从组建挂载开始来看虚拟dom在vue/src/core/instance/lifecycle.js中的mountComponent方法,里面最主要的是有一个更新组件的方法

    updateComponent = () => {
    // 实际调用是在lifeCycleMixin中定义的_update和renderMixin中定义的_render
      vm._update(vm._render(), hydrating)
    }
// Vue原型对象挂载_update的方法,传入虚拟节点,和hydrating
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.
    // Vue原型的__patch__注入入口,基于使用的渲染后端
    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
    // 如果父节点是高阶组件,也像$el一样更新他
    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__
platforms/web/runtime/index.js

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值