2. vue源码分析——数据驱动

数据驱动是vue的一个核心思想 本篇我们的目标就是要搞懂我们写入的插值表达式是怎样渲染成dom的 这期间究竟发生了什么

1. new Vue发生了什么
在看之前 我希望你已经了解了vue的执行流程 不了解的朋友可以看我的上一篇博客
这里我们先从src/core/instance/inde.js文件开始
在这里插入图片描述
当我们new Vue时 首先vue做了警告处理 然后初始化了options 但是我们在这里并没有看到_init的定义 它其实是在initMixin方法里定义的 这里我们顺藤摸瓜进入./init
在这里插入图片描述
在这里插入图片描述
由于代码有点长 一屏截不下 所以这里截取了两次
我们可以看到_init方法做了各种初始化操作 这里我们先看merge操作 _init把new Vue时传进的options对象合并到了vm.$options上 在这个地方有个常见的问题 就是为什么我们通过this.xxx可以直接访问到我们传进去的data.xxx 而不需要this.data.xxx
这里我们找到initState方法 它定义在/.state里 我们进入./state
在这里插入图片描述
initState方法首先判断是否存在data 然后调用了initData方法
在这里插入图片描述
initData首先拿到data并赋值给vm._data 然后判断data是否是函数 如果是函数调用getData拿到返回值(也就是data)如果是对象 则直接赋值 如果没传则为空对象 拿到处理完成的data数据后 就是一些警告处理了 这里我们直接看最后 调用了proxy(vm, ‘_data’, key)方法
在这里插入图片描述
我们可以看到 proxy使用es5提供的Object.defineProperty做了一层代理 当我们访问vm.xxx时 它就会返回vm._data.xxx 这也就是为什么我们可以直接通过this.xxx访问到data的原因了

然后我们回到initMixin我们发现最后调用了vm.$mount方法 这里我可以提前透露一下 当我们一调用这个方法之后 所有的插值都会被替换 dom就完成了更新 这个方法是我们下面分析的重点
总结:new Vue发生了什么 首先调用原型上的_init方法 然后合并options到vm上 然后initState方法代理了vm 使我们可以直接通过this.xxx访问到data 最后调用了mount方法完成插值替换

2. vue 中实例挂载的实现
现在我们来看本次的重点$mount方法 由于我们分析的是runtime+compiler版本 所有我们打开entry-runtime-with-compiler入口文件

//这里我们只截取需要的代码
//entry-runtime-with-compiler入口首先从./runtime/index引入了vue
import Vue from './runtime/index' 

//缓存原有$mount 
const mount = Vue.prototype.$mount
//重新定义$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
	//拿到el
  el = el && query(el)

  /* istanbul ignore if */ 警告处理 el不能挂载到body下
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
//拿到$options
  const options = this.$options
  // resolve template/el and convert to render function

  //通过用户有无传render函数做相应处理
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    //上面一堆逻辑都是为了拿到template 如果没有template 就通过el拿到
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      JSON.stringify
      //然后通过compileToFunctions函数把template转化为render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      //然后把通过template编译好的render函数挂载到vm.$options上
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }

  //最后调用缓存的原有$mount
  return mount.call(this, el, hydrating)
}

接下来我们来看原有的$mount是在哪里定义的 我们找到上个文件也就是引入vue的文件./runtime/index

//我们同样只截取需要的代码
import Vue from 'core/index'
import { mountComponent } from 'core/instance/lifecycle'

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  //拿到el
  el = el && inBrowser ? query(el) : undefined
  //然后调用从core/instance/lifecycle引入的mountComponent方法
  return mountComponent(this, el, hydrating)
}

然后我们顺藤摸瓜打开core/instance/lifecycle找到这个方法

//同样只截取需要的代码
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
  //如果没有渲染函数 就创建一个空的vnode
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        //警告处理 如果使用了runtime-only版本 但是你却使用了template就抛出一个警告
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  //调用声明周期钩子beforeMount 这里我们不做过多的讲解 后期会有专门对生命周期做分析
  callHook(vm, 'beforeMount')
  
  //这里声明了 updateComponent方法 用来更新组件
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    //mark和性能埋点有关 不是我们考虑的重点
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    //然后我们看updateComponent做了什么 
    //调用了实例上的_update方法
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  
  //定义了updateComponent方法之后 把他传给了观察者 这个非常重要 它和响应式强相关 这里你先理解当数据发生变化时 
  //观察者就会调用updateComopnent方法重新渲染
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

有些人可能会有些疑惑为什么要定义两次$mount方法 这其实是为了实现runtime-compiler版本 如果是在runtime-only版本中 就仅仅只有runtime/index中的定义
总结:这里我们以runtime-compiler版本为例 首先缓存原有的mount方法 然后判断开发者是否传render函数 如果没有 则通过el或所传的template拿到template 然后通过编译转化为render函数并挂载到options上 最后调用原有的mount方法 原来的mount方法会调用mountComponent方法 mountComponent方法会判断有没有渲染函数 然后做一些警告处理 然后声明和定义updateComponent方法 用来更新组件 最后把定义好的updateComponent方法传给Watcher的实例观察者 一旦数据发生变化 观察者就会调用updateComponent方法触发vm._update方法更新组件完成响应式

3. vm._render的实现
vm._render定义在instance/render.js中

//这里我们只截取需要的代码 
export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  //_render定义在renderMixin方法中 renderMixin在instance/index中调用
  //_render函数最终返回一个vnode
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    //拿到options上的render函数 这个render函数可以开发者传的 也可以是通过template编译过来的
    const { render, _parentVnode } = vm.$options
   
	//这里的vnode我们下面会将 先不考虑 我们只看render
    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.
      currentRenderingInstance = vm


      //-> vnode
      //render函数调用 传入vm.$createElement方法 并把执行上下文指向vm._renderProxy最终返回vnode
      //vm.$createElement方法就是我们写render函数的creatElement方法
      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)) {
      //如果vnode是数组 抛出警告 必须只能一个root 这个错误刚接触vue的朋友应该都看过
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    //最终返回vnode
    return vnode
  }
}

下面我们来看看vm.$createElement方法

//vm.createElement方法在initRender方法中定义 initRender在instance/init.js中的_init方法中执行
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  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
  //编译生成render函数使用的方法
  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.
  //手写render函数使用的方法 vm.$createElment调用createElment方法
  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
  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)
  }
}

4. VDom
为了方便下面的了解 这里我们先简单介绍一下虚拟dom 对于这个名词 我想都不陌生了 我们知道浏览器中的dom是非常昂贵的 一个普通的dom元素都拥有非常多的属性 非常的庞大 如果我们平凡的操作 就会存在性能问题 而vdom是用一个原生的js对象去描述一个dom节点 他的创建比dom的代价小的多 我们可以看下vue中的dom节点
在这里插入图片描述
我们发现通过对象的方式来描述一个dom节点不但可以减少真实dom操作的性能损耗 而且操作起来非常的方便
小扩展:vue的vdom参考snabbdom库

5.createElement方法
vue使用createElement方法创建vnode 它定义在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> {
  //参数重载 如果没有传data 后面的参数往前移
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  //通过判断render函数是否是编译而来给normalizationType赋不同的值
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  //然后调用_createElement方法
  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> {
  //判断data是否为响应式 如果是响应式 抛出警告 不允许vnode中的data为响应式 响应式部分后面会讲 这里不做过多分析
  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()
  }
  //这些都是对参数的一些边界处理 跳过
  // 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()
  }
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // 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
  }
  //这里需要看下 对不同情况下的children做扁平化处理
  if (normalizationType === ALWAYS_NORMALIZE) {
    //手写render函数情况下
    //children递归扁平化
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    //通过template编译生成的render函数情况下
    //children一层扁平化
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  //然后就是一堆判断处理 最终返回vnode
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

6.vm._update方法
拿到vnode之后 我们回到instance/lifecyle.js中的mountComponent方法中
在这里插入图片描述
然后我们来看_update方法
_update方法定义在instance/lifecycle.js中的lifecycleMixin方法中
在这里插入图片描述
然后我们打开runtime/index.js
在这里插入图片描述
我们在打开./patch
在这里插入图片描述
这里使用闭包的技巧平台差异 通过平台创建出属于自己的patch方法 然后我们打开core/vdom/patch.js
在这里插入图片描述
由于这个方法过于长 里面封装了许多辅助方法 这里我们只截取一小部分看看

return function patch (oldVnode, vnode, hydrating, removeOnly) {
    //删除时的逻辑 这里先跳过
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      //
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          //服务端渲染 pass
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          //走不到 pass
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it

          //emptyNodeAt真实的dom转化为vnode
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        //oldElm就是真实的dom
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        //这个很重要 把vnode挂载到真实的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)
        )

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }

createPatchFunction方法最后返回patch方法
阅读以上之后 我们再细看createElm方法

function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    //走不到
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    //先不看
    vnode.isRootInsert = !nested // for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      //组件注册使用 报错 跳过
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }
      }

      //这里就比较重要了 就是创建真实dom nodeOps对象里面放着许多操作dom的方法 之前有讲过 这里就不在解释了
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      //weex平台逻辑 跳过
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        //创建子节点
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        //然后插入 真实的插入dom就是靠这个insert方法
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

可能代码有点多 看的有点头大 这里我做了个总结 你顺着这个思路看源码 然后在结合这些一起理解理解会好很多
在这里插入图片描述
如果runtime+compile版本 就需要compile生成render函数 如果是runtime-only版本 就不需要comile版本了

源码分析系列均借鉴huangyi老师的vue源码分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值