Vue 2.0 相比 Vue 1.0 最大的降级就是利用了虚构DOM。 在 Vue 1.0 中视图的更新是纯响应式的。在进行响应式初始化的时候,一个响应式数据 key 会创立一个对应的 dep,这个 key 在模板中被援用几次就会创立几个 watcher。也就是一个 key 对应一个 depdep 内治理一个或者多个 watcher。因为 watcherDOM 是一对一的关系,更新时,能够明确的对某个 DOM 进行更新,更新效率还是很高的。

随着利用越来越大,组件越来越多,一个组件就可能存在大量的 watcher ,性能就成了问题。Vue 2.0 退出了虚构DOM和 diff 后,一个组件就只须要创立一个 watcher 了,更新形式应用响应式+diff,组件之间应用响应式,组件外部应用 diff 。当数据发生变化时,告诉 watcher 更新,也就是告诉整个组件更新,具体更新什么元素什么地位,就能够通过 diff 算法比照新旧虚构DOM获取差别,把差别真正的更新到视图。

虚构DOM在 React、Vue 都有使用,一方面能够晋升一些性能,另一方面也能够更好的跨平台,比方服务端渲染。

虚构DOM,也就是VNode,实质上是应用 js 对象来模仿实在DOM中存在的节点。举个栗子:

<div id="app">  <h1>虚构DOM<h1></div>
{  tag: "div",  attrs: {    id: "app"  },  children: [    {      tag: "h1",      text: "虚构DOM"    }  ]}

VNode

Vue 中的虚构DOM是用 一个 Class 去形容的。咱们先来看看:

// src/core/vdom/vnode.jsexport 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  key: string | number | void;  componentOptions: VNodeComponentOptions | void;  componentInstance: Component | void; // component instance  parent: VNode | void; // component placeholder node  // strictly internal  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?  asyncFactory: Function | void; // async component factory function  asyncMeta: Object | void;  isAsyncPlaceholder: boolean;  ssrContext: Object | void;  fnContext: Component | void; // real context vm for functional nodes  fnOptions: ?ComponentOptions; // for SSR caching  devtoolsMeta: ?Object; // used to store functional render context for devtools  fnScopeId: ?string; // functional scope id support  constructor (    tag?: string,    data?: VNodeData,    children?: ?Array<VNode>,    text?: string,    elm?: Node,    context?: Component,    componentOptions?: VNodeComponentOptions,    asyncFactory?: Function  ) {    this.tag = tag    this.data = data    this.children = children    this.text = text    this.elm = elm    this.ns = undefined    this.context = context    this.fnContext = undefined    this.fnOptions = undefined    this.fnScopeId = 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    this.asyncFactory = asyncFactory    this.asyncMeta = undefined    this.isAsyncPlaceholder = false  }  // DEPRECATED: alias for componentInstance for backwards compat.  /* istanbul ignore next */  get child (): Component | void {    return this.componentInstance  }}

_render

在之前介绍过,响应式数据发生变化时,触发更新其实就是执行上面代码:

updateComponent = () => {  vm._update(vm._render(), hydrating)}

先执行 vm._render() ,获取VNode ,做为参数传递给 vm._update 去更新视图。咱们先看看 vm._render() 的定义:

// src/core/instance/render.jsexport function renderMixin (Vue: Class<Component>) {  // install runtime convenience helpers  // 在组件实例上挂载一些运行时须要用到的工具办法  installRenderHelpers(Vue.prototype)  Vue.prototype.$nextTick = function (fn: Function) {    return nextTick(fn, this)  }  /**   * 通过执行 render 函数生成 VNode   * 不过外面加了大量的异样解决代码   */  Vue.prototype._render = function (): VNode {    const vm: Component = this    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.    // 设置父 vnode。这使得渲染函数能够拜访占位符节点上的数据。    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      // 执行 render 函数,生成 vnode      vnode = render.call(vm._renderProxy, vm.$createElement)    } catch (e) {      handleError(e, vm, `render`)      // 错误处理,开发环境渲染错误信息,生产环境返回之前的 vnode,以避免渲染谬误导致组件空白      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    }    // 如果 vnode 是数组且只有一项,间接放回该项    if (Array.isArray(vnode) && vnode.length === 1) {      vnode = vnode[0]    }    // render 函数出错时,返回一个空的 vnode    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  }}

_render 中会调用 render 办法,这里有两种状况:

  • 模板编译而来的 render

    with(this){return _c(tag, data, children, normalizationType)}
  • 用户定义的 render

    render: function (createElement) {return createElement('div', {   attrs: {      id: 'app'    },}, this.msg)}

    下面能够看出模板编译进去的 render 理论就是调用 _c 办法,用户自定义的 render 理论就是 vm.$createElement 办法。

    // src/core/instance/render.jsexport function initRender (vm: Component) {/** * @param {*} a 标签名 * @param {*} b 属性的 JSON 字符串 * @param {*} c 子节点数组 * @param {*} d 节点的规范化类型 * @returns VNode or Array<VNode> */  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)}

    这两个办法反对雷同的参数,理论调用的都是 createElement 办法。

createElement

// src/core/vdom/create-element.js// 生成组件或一般标签的 vnode,一个包装器函数,用于提供更灵便的接口。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  }  // 执行 _createElement 办法创立组件的 VNode  return _createElement(context, tag, data, children, normalizationType)}

_createElement

// src/core/vdom/create-element.jsexport 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__)) {    // 如果 data 是一个响应式对象,返回空节点的Vnode    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) {    // 动静组件:is 属性被设置为 false 时    // 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  }  // 子节点标准化  if (normalizationType === ALWAYS_NORMALIZE) {    children = normalizeChildren(children)  } else if (normalizationType === SIMPLE_NORMALIZE) {    children = simpleNormalizeChildren(children)  }  let vnode, ns  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) && data.tag !== 'component') {        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))) {      // 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      // 未知的标签 tag ,在运行时查看,因为当其父项规范化子项时,可能会为其调配命名空间      vnode = new VNode(        tag, data, children,        undefined, undefined, context      )    }  } else {    // tag 非字符串时    // 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()  }}

_createElement办法内一开先是对一些非凡状况的判断,咱们只关注主流程,也就是生成 VNode 这一块,这里对 tag 先是判断如果是 string 类型,有三种状况:

  • 如果是一些平台内置节点,间接创立一个一般的 VNode.
  • 如果为已注册的组件名,通过 createComponent 创立一个组件类型的 VNode.
  • 否则创立一个未知标签名的 VNode.

这里还有一个值得注意的中央就是通过 normalizeChildrensimpleNormalizeChildren 办法对子节点进行规范化,使其每个节点都是 VNode 类型。

simpleNormalizeChildren 办法调用场景是 render 函数是编译生成的。实践上编译生成的 children 都曾经是 VNode 类型的,但这里有一个例外,就是 functional component 函数式组件返回的是一个数组而不是一个根节点,所以会通过 Array.prototype.concat 办法把整个 children 数组打平,让它的深度只有一层。

normalizeChildren 办法的调用场景有 2 种,一个场景是 render 函数是用户手写的,当 children 只有一个节点的时候,Vue.js 从接口层面容许用户把 children 写成根底类型用来创立单个简略的文本节点,这种状况会调用 createTextVNode 创立一个文本节点的 VNode;另一个场景是当编译 slot、v-for 的时候会产生嵌套数组的状况,会调用 normalizeArrayChildren 办法

组件类型

createComponent

// src/core/vdom/create-component.jsexport 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  }  // context.$options._base = Vue.options._base = Vue  const baseCtor = context.$options._base  // Vue.extend 定义在 src/core/global-api/extend.js  // 当 Ctor 为配置对象时,通过 Vue.extend 结构一个 Vue 的子类  // 当 Ctor 是一个函数式,示意是异步组件,就不会执行 Vue.extend  if (isObject(Ctor)) {    Ctor = baseCtor.extend(Ctor)  }  // if at this stage it's not a constructor or an async component factory,  // reject.  if (typeof Ctor !== 'function') {    if (process.env.NODE_ENV !== 'production') {      warn(`Invalid Component definition: ${String(Ctor)}`, context)    }    return  }  // async component (异步组件)  let asyncFactory  if (isUndef(Ctor.cid)) {    // 如果Ctor.cid为空,那么Ctor就是一个函数,表明这是一个异步组件    asyncFactory = Ctor    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)    if (Ctor === undefined) {      // return a placeholder node for async component, which is rendered      // as a comment node but preserves all the raw information for the node.      // the information will be used for async server-rendering and hydration.      // 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration      return createAsyncPlaceholder(        asyncFactory,        data,        context,        children,        tag      )    }  }  // 节点的属性 JSON 字符串  data = data || {}  // 合并选项,在组件构造函数创立后利用全局混合的状况下解析构造函数选项  // resolve constructor options in case global mixins are applied after  // component constructor creation  resolveConstructorOptions(Ctor)  // 将组件的 v-model 转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调  // transform component v-model data into props & events  if (isDef(data.model)) {    transformModel(Ctor.options, data)  }  // 提取 props 数据,失去 propsData 对象,propsData[key] = val  // extract props  const propsData = extractPropsFromVNodeData(data, Ctor, tag)  // functional component (函数式组件)  if (isTrue(Ctor.options.functional)) {    return createFunctionalComponent(Ctor, propsData, data, context, children)  }  // 提取事件侦听,因为这些侦听器须要被视为子组件侦听器,而不是DOM侦听器  // extract listeners, since these needs to be treated as  // child component listeners instead of DOM listeners  const listeners = data.on  // 替换为带有.native修饰符的侦听器,以便在父组件修补程序期间对其进行解决。  // replace with listeners with .native modifier  // so it gets processed during parent component patch.  data.on = data.nativeOn  if (isTrue(Ctor.options.abstract)) {    // 形象组件只保留 props、listeners 和 slot    // abstract components do not keep anything    // other than props & listeners & slot    // work around flow    const slot = data.slot    data = {}    if (slot) {      data.slot = slot    }  }  // 装置组件钩子函数 init、prepatch、insert、destroy  // install component management hooks onto the placeholder node  installComponentHooks(data)  // 通过 new VNode 实例化一个 vnode 并返回  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  )  // Weex specific: invoke recycle-list optimized @render function for  // extracting cell-slot template.  // https://github.com/Hanks10100/weex-native-directive/tree/master/component  /* istanbul ignore if */  if (__WEEX__ && isRecyclableComponent(vnode)) {    return renderRecyclableComponentTemplate(vnode)  }  return vnode}

从下面代码能够看出,对异步组件、函数式组件和一般组件都别离做了解决。就一般组件而言,createComponent 办法大抵做了这么几件事:

  • 通过 Vue.extend 构建子类的构造函数。
  • 装置组件钩子函数 initprepatchinsertdestroy
  • 实例化 VNode,返回 VNode。

createComponent 内的逻辑有点简单,咱们依照几种组件的类别,先来看一般组件:

resolveConstructorOptions

// src/core/instance/init.js/** * @description: 从组件构造函数中解析配置对象 options,并合并基类选项 * @param {*} Ctor * @return {Object} options */export function resolveConstructorOptions (Ctor: Class<Component>) {  // 从实例构造函数上获取选项  let options = Ctor.options  if (Ctor.super) {    // 存在基类,递归解析基类构造函数的选项    const superOptions = resolveConstructorOptions(Ctor.super)    // 缓存    const cachedSuperOptions = Ctor.superOptions    if (superOptions !== cachedSuperOptions) {      // 阐明基类的配置项产生了更改      Ctor.superOptions = superOptions      // 找到更改的选项      const modifiedOptions = resolveModifiedOptions(Ctor)      // 如果存在被批改或减少的选项,则合并两个选项      if (modifiedOptions) {        // 将更改的选项和 extend 选项合并        extend(Ctor.extendOptions, modifiedOptions)      }      // 将新的选项赋值给 options      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)      if (options.name) {        options.components[options.name] = Ctor      }    }  }  return options}

resolveModifiedOptions

// src/core/instance/init.js/** * @description: 解析构造函数选项中后续被批改或者减少的选项 * @param {*} Ctor * @return {*} modified 不统一的选项 */function resolveModifiedOptions (Ctor: Class<Component>): ?Object {  let modified  // 构造函数选项  const latest = Ctor.options  // 密封的构造函数选项,备份  const sealed = Ctor.sealedOptions  // 比照两个选项,记录不统一的选项  for (const key in latest) {    if (latest[key] !== sealed[key]) {      if (!modified) modified = {}      modified[key] = latest[key]    }  }  return modified}

transformModel

// src/core/vdom/create-component.js/** * 将组件的 v-model 转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 * transform component v-model info (value and callback) into * prop and event handler respectively. */function transformModel (options, data: any) {  // model 的属性和事件,默认为 value 和 input  const prop = (options.model && options.model.prop) || 'value'  const event = (options.model && options.model.event) || 'input'    // 在 data.attrs 对象上存储 v-model 的值    ; (data.attrs || (data.attrs = {}))[prop] = data.model.value  // 在 data.on 对象上存储 v-model 的事件  const on = data.on || (data.on = {})  // 已存在的事件回调函数  const existing = on[event]  // v-model 中事件对应的回调函数  const callback = data.model.callback  // 合并回调函数  if (isDef(existing)) {    if (      Array.isArray(existing)        ? existing.indexOf(callback) === -1        : existing !== callback    ) {      on[event] = [callback].concat(existing)    }  } else {    on[event] = callback  }}

extractPropsFromVNodeData

// src/core/vdom/helpers/extract-props.jsexport function extractPropsFromVNodeData (  data: VNodeData,  Ctor: Class<Component>,  tag?: string): ?Object {  // 只提取原始值,验证和默认值在子组件中解决  // we are only extracting raw values here.  // validation and default values are handled in the child  // component itself.  const propOptions = Ctor.options.props  // 未定义 props  if (isUndef(propOptions)) {    return  }  const res = {}  const { attrs, props } = data  if (isDef(attrs) || isDef(props)) {    // 遍历 propsOptions    for (const key in propOptions) {      // 将小驼峰模式的 key 转换为 xxx-xxx-xxx 模式      const altKey = hyphenate(key)      if (process.env.NODE_ENV !== 'production') {      // 谬误提醒        const keyInLowerCase = key.toLowerCase()        if (          key !== keyInLowerCase &&          attrs && hasOwn(attrs, keyInLowerCase)        ) {          tip(            `Prop "${keyInLowerCase}" is passed to component ` +            `${formatComponentName(tag || Ctor)}, but the declared prop name is` +            ` "${key}". ` +            `Note that HTML attributes are case-insensitive and camelCased ` +            `props need to use their kebab-case equivalents when using in-DOM ` +            `templates. You should probably use "${altKey}" instead of "${key}".`          )        }      }      checkProp(res, props, key, altKey, true) ||        checkProp(res, attrs, key, altKey, false)    }  }  return res}

checkProp

// src/core/vdom/helpers/extract-props.jsfunction checkProp (  res: Object,  hash: ?Object,  key: string,  altKey: string,  preserve: boolean): boolean {  if (isDef(hash)) {    // 判断 hash(props/attrs)对象中是否存在 key 或 altKey    // 存在则设置给 res => res[key] = hash[key]    if (hasOwn(hash, key)) {      res[key] = hash[key]      if (!preserve) {        delete hash[key]      }      return true    } else if (hasOwn(hash, altKey)) {      res[key] = hash[altKey]      if (!preserve) {        delete hash[altKey]      }      return true    }  }  return false}

installComponentHooks

installComponentHooks 的作用就是把 componentVNodeHooks 的钩子函数合并到 data.hook 中,在合并过程,如果某个钩子存在,执行 mergeHook 函数做合并。在 VNode 执行 patch 的过程中会执行相干的钩子函数。

// src/core/vdom/create-component.js/** * 在组件的 data 对象上设置 hook 对象, * hook 对象减少四个属性,init、prepatch、insert、destroy, * 负责组件的创立、更新、销毁 */function installComponentHooks (data: VNodeData) {  const hooks = data.hook || (data.hook = {})  // 遍历 hooksToMerge 数组,hooksToMerge = ['init', 'prepatch', 'insert' 'destroy']  for (let i = 0; i < hooksToMerge.length; i++) {    // 比方 key = init    const key = hooksToMerge[i]    // 从 data.hook 对象中获取 key 对应的办法    const existing = hooks[key]    // componentVNodeHooks 对象中 key 对象的办法    const toMerge = componentVNodeHooks[key]    // 合并用户传递的 hook 办法和框架自带的 hook 办法,其实就是别离执行两个办法    if (existing !== toMerge && !(existing && existing._merged)) {      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge    }  }}function mergeHook (f1: any, f2: any): Function {  const merged = (a, b) => {    // flow complains about extra args which is why we use any    f1(a, b)    f2(a, b)  }  merged._merged = true  return merged}

componentVNodeHooks

// src/core/vdom/create-component.js// patch 期间在组件 VNode 上调用的内联钩子// inline hooks to be invoked on component VNodes during patchconst componentVNodeHooks = {  // 初始化  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {    if (      vnode.componentInstance &&      !vnode.componentInstance._isDestroyed &&      vnode.data.keepAlive    ) {      // 被 keep-alive 包裹的组件      // kept-alive components, treat as a patch      const mountedNode: any = vnode // work around flow      componentVNodeHooks.prepatch(mountedNode, mountedNode)    } else {      // 创立组件实例,即 new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例      const child = vnode.componentInstance = createComponentInstanceForVnode(        vnode,        activeInstance      )      // 调用 $mount 办法,进入挂载阶段。      child.$mount(hydrating ? vnode.elm : undefined, hydrating)    }  },  // 更新 VNode  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {    // 新的 VNode 配置项    const options = vnode.componentOptions    // 旧的 VNode 组件实例    const child = vnode.componentInstance = oldVnode.componentInstance    // 用新的更新旧的    updateChildComponent(      child,      options.propsData, // updated props      options.listeners, // updated listeners      vnode, // new parent vnode      options.children // new children    )  },  // 执行组件的 mounted 钩子函数  insert (vnode: MountedComponentVNode) {    const { context, componentInstance } = vnode    if (!componentInstance._isMounted) {      componentInstance._isMounted = true      callHook(componentInstance, 'mounted')    }    // 解决 keep-alive 组件的异常情况    if (vnode.data.keepAlive) {      if (context._isMounted) {        // vue-router#1212        // During updates, a kept-alive component's child components may        // change, so directly walking the tree here may call activated hooks        // on incorrect children. Instead we push them into a queue which will        // be processed after the whole patch process ended.        queueActivatedComponent(componentInstance)      } else {        activateChildComponent(componentInstance, true /* direct */)      }    }  },  // 销毁  destroy (vnode: MountedComponentVNode) {    // 获取组件实例    const { componentInstance } = vnode    // 已销毁的组件跳过    if (!componentInstance._isDestroyed) {      if (!vnode.data.keepAlive) {        // 间接调用 $destroy 销毁组件        componentInstance.$destroy()      } else {        // 被 keep-alive 包裹的组件        // 让组件失活,不销毁组件实例,从而缓存组件的状态        deactivateChildComponent(componentInstance, true /* direct */)      }    }  }}

createComponentInstanceForVnode

// src/core/vdom/create-component.js// new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例 export function createComponentInstanceForVnode (  // we know it's MountedComponentVNode but flow doesn't  vnode: any,  // activeInstance in lifecycle state  parent: any): Component {  const options: InternalComponentOptions = {    _isComponent: true,    _parentVnode: vnode,    parent  }  // 查看内联模版渲染函数  const inlineTemplate = vnode.data.inlineTemplate  if (isDef(inlineTemplate)) {    options.render = inlineTemplate.render    options.staticRenderFns = inlineTemplate.staticRenderFns  }  // new VueComponent(options) => Vue 实例  return new vnode.componentOptions.Ctor(options)}

最初就是实例化 VNode,返回 VNode。除了一般组件,还有分支:异步组件和函数组件。上面看看异步组件和函数组件的流程:

异步组件

// src/core/vdom/create-component.js —— createComponent 办法中// async component (异步组件)let asyncFactoryif (isUndef(Ctor.cid)) {  asyncFactory = Ctor  Ctor = resolveAsyncComponent(asyncFactory, baseCtor)  if (Ctor === undefined) {    // return a placeholder node for async component, which is rendered    // as a comment node but preserves all the raw information for the node.    // the information will be used for async server-rendering and hydration.    // 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration    return createAsyncPlaceholder(      asyncFactory,      data,      context,      children,             tag    )  }}

resolveAsyncComponent

// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent (  factory: Function,  baseCtor: Class<Component>): Class<Component> | void {  if (isTrue(factory.error) && isDef(factory.errorComp)) {    return factory.errorComp  }  if (isDef(factory.resolved)) {    return factory.resolved  }  // owner 实例收集容器,对同一个异步组件的援用不用屡次解析,而是将以后应用该异步组件的实例收集起来,待到异步组件解析结束,挨个告诉渲染更新即可  const owner = currentRenderingInstance  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {    // already pending    factory.owners.push(owner)  }  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {    return factory.loadingComp  }  //...  // 这块代码有点长,咱们在上面把它分为:一般异步组件、Promise异步组件,高级异步组件,离开来看}

针对一般函数的状况,后面几个 if 判断能够疏忽,它们是为高级组件所用,对于 factory.xxxxx 的判断,是思考到多个中央同时初始化一个异步组件,那么它的理论加载应该只有一次。

createAsyncPlaceholder

// src/core/vdom/helpers/resolve-async-component.js// 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息export function createAsyncPlaceholder (  factory: Function,  data: ?VNodeData,  context: Component,  children: ?Array<VNode>,  tag: ?string): VNode {  const node = createEmptyVNode()  node.asyncFactory = factory  node.asyncMeta = { data, context, children, tag }  return node}

一般异步组件

Vue.component('async-example', function (resolve, reject) {   // 这个非凡的 require 语法通知 webpack   // 主动将编译后的代码宰割成不同的块,   // 这些块将通过 Ajax 申请主动下载。   require(['./my-async-component'], resolve)   } )

Vue 容许将组件定义为一个工厂函数,动静的解析组件,Vue 只在组件须要渲染时触发工厂函数,并且把后果缓存起来,用于前面的再次渲染。

// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent (  factory: Function,  baseCtor: Class<Component>): Class<Component> | void {   //一般异步组件第二次执行这里时会返回factory.resolved  if (isDef(factory.resolved)) {    return factory.resolved  }  // owner 实例收集容器,对同一个异步组件的援用不用屡次解析,而是将以后应用该异步组件的实例收集起来,待到异步组件解析结束,挨个告诉渲染更新即可  const owner = currentRenderingInstance  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {    // already pending    factory.owners.push(owner)  }   if (owner && !isDef(factory.owners)) {    // owner 实例收集容器    const owners = factory.owners = [owner]    // sync 同步标识符,标识以后是同步还是异步    let sync = true    let timerLoading = null    let timerTimeout = null      ; (owner: any).$on('hook:destroyed', () => remove(owners, owner))    const forceRender = (renderCompleted: boolean) => {      // 顺次调用该元素的 $forceUpdate()办法 该办法会强制渲染一次      for (let i = 0, l = owners.length; i < l; i++) {        (owners[i]: any).$forceUpdate()      }      // 组件更新之后 移除定时器,清空依赖      if (renderCompleted) {        owners.length = 0        if (timerLoading !== null) {          clearTimeout(timerLoading)          timerLoading = null        }        if (timerTimeout !== null) {          clearTimeout(timerTimeout)          timerTimeout = null        }      }    }    // 定义一个 resolve 函数, once是一次性包装函数,保障传入的函数只执行一次(防止屡次执行告诉更新操作)    const resolve = once((res: Object | Class<Component>) => {      // 缓存 resolved      factory.resolved = ensureCtor(res, baseCtor)      // invoke callbacks only if this is not a synchronous resolve      // (async resolves are shimmed as synchronous during SSR)      if (!sync) {        forceRender(true)      } else {        owners.length = 0      }    })    //定义一个 reject 函数    const reject = once(reason => {      process.env.NODE_ENV !== 'production' && warn(        `Failed to resolve async component: ${String(factory)}` +        (reason ? `\nReason: ${reason}` : '')      )      if (isDef(factory.errorComp)) {        factory.error = true        forceRender(true)      }    })    //执行 factory()函数    const res = factory(resolve, reject)        sync = false    // return in case resolved synchronously    return factory.loading      ? factory.loadingComp      : factory.resolved  }}

resolveAsyncComponent 外部会定义一个 resolvereject 函数,而后执行 factory() 函数,factory() 就是咱们在组件定义的函数,函数内会执行 require 函数,因为 require() 是个异步操作,所以 resolveAsyncComponent 就会返回 undefined

回到 createComponent 函数,因为返回的是 undefined ,则会执行 createAsyncPlaceholder 去创立一个正文节点占位符。

在下一个 tick 等 require 加载胜利后就会执行 resolve 函数,也就是在 resolveAsyncComponent内定义的 resolve 函数,resolve 函数会将后果保留到工厂函数的 resolved 属性里。

再次从新渲染执行到 resolveAsyncComponent 的时候 factory.resolved 存在了,就间接返回。

Promise异步组件

Vue.component(  'async-webpack-example',  // 该 `import` 函数返回一个 `Promise` 对象。  () => import('./my-async-component'))

webpack 2+ 反对了异步加载的语法糖:() => import('./my-async-component') ,当执行完 res = factory(resolve, reject) ,返回的值就是 import('./my-async-component')  的返回值,它是一个 Promise 对象。

// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent (  factory: Function,  baseCtor: Class<Component>): Class<Component> | void {  // 组件第二次执行这里时会返回factory.resolved  if (isDef(factory.resolved)) {    return factory.resolved  }  if (owner && !isDef(factory.owners)) {    // ...    // 执行 factory()函数,    // 返回一个含有then的对象    const res = factory(resolve, reject)        if (isObject(res)) {      if (isPromise(res)) {        // () => Promise        if (isUndef(factory.resolved)) {          // 如果 factory.resolved 不存在          // 用 then 办法指定 resolve 和 reject 的回调函数          res.then(resolve, reject)        }      } else if (isPromise(res.component)) {      // ...      }    }    sync = false    // return in case resolved synchronously    return factory.loading      ? factory.loadingComp      : factory.resolved  }}

当组件异步加载胜利后,执行 resolve,加载失败则执行 reject,很好的配合 webpack 2+ 的异步加载组件的形式(Promise)。

高级异步组件

高级异步组件能够定义更多的状态,比方加载该组件的超时工夫、加载过程中显式的组件、出错时显式的组件、延迟时间等。

const AsyncComp = () => ({  component: import('./MyComp.vue'),  loading: LoadingComp,  error: ErrorComp,  delay: 200,  timeout: 3000})Vue.component('async-example', AsyncComp)

对于高级异步组件来说,他和promise()办法加载的逻辑是一样的,不同的是多了几个属性:

// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent (  factory: Function,  baseCtor: Class<Component>): Class<Component> | void {  //...  if (owner && !isDef(factory.owners)) {    // ...    //执行 factory()函数    const res = factory(resolve, reject)    if (isObject(res)) {      if (isPromise(res)) {        // () => Promise        if (isUndef(factory.resolved)) {          res.then(resolve, reject)        }      } else if (isPromise(res.component)) {        // 高级异步组件的分支        res.component.then(resolve, reject)        if (isDef(res.error)) {          // 失败时的模块          factory.errorComp = ensureCtor(res.error, baseCtor)        }        if (isDef(res.loading)) {          // 如果有设置加载时的模块          factory.loadingComp = ensureCtor(res.loading, baseCtor)          if (res.delay === 0) {            // 如果等待时间为0            factory.loading = true          } else {            timerLoading = setTimeout(() => {              timerLoading = null              if (isUndef(factory.resolved) && isUndef(factory.error)) {                factory.loading = true                forceRender(false)              }            }, res.delay || 200)          }        }        if (isDef(res.timeout)) {          // 超时工夫          timerTimeout = setTimeout(() => {            timerTimeout = null            if (isUndef(factory.resolved)) {              reject(                process.env.NODE_ENV !== 'production'                  ? `timeout (${res.timeout}ms)`                  : null              )            }          }, res.timeout)        }      }    }    sync = false    // return in case resolved synchronously    return factory.loading      ? factory.loadingComp      : factory.resolved  }}

函数式组件

// src/core/vdom/create-component.js —— createComponent 办法中// functional component (函数式组件)if (isTrue(Ctor.options.functional)) {  return createFunctionalComponent(Ctor, propsData, data, context, children) }

createFunctionalComponent

// src/core/vdom/create-functional-component.jsexport function createFunctionalComponent (  Ctor: Class<Component>,  propsData: ?Object,  data: VNodeData,  contextVm: Component,  children: ?Array<VNode>): VNode | Array<VNode> | void {  //配置项  const options = Ctor.options  // props  const props = {}  // 组件自身的 props  const propOptions = options.props  // 设置函数式组件的 props 对象  if (isDef(propOptions)) {    // 函数式组件自身提供了 props 选项,则将 props.key 的值设置为组件上传递下来的对应 key 的值    for (const key in propOptions) {      props[key] = validateProp(key, propOptions, propsData || emptyObject)    }  } else {    // 函数式组件没有提供 props 选项,则将组件上的 attribute 主动解析为 props    if (isDef(data.attrs)) mergeProps(props, data.attrs)    if (isDef(data.props)) mergeProps(props, data.props)  }  // 实例化函数式组件的渲染上下文  const renderContext = new FunctionalRenderContext(    data,    props,    children,    contextVm,    Ctor  )  // 调用 render 函数,生成 vnode,并给 render 函数传递 _c 和 渲染上下文  const vnode = options.render.call(null, renderContext._c, renderContext)  // 生成的 VNode 对象上加一些标记,示意该 VNode 是一个函数式组件生成的,最初返回 VNode  if (vnode instanceof VNode) {    return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)  } else if (Array.isArray(vnode)) {    const vnodes = normalizeChildren(vnode) || []    const res = new Array(vnodes.length)    for (let i = 0; i < vnodes.length; i++) {      res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)    }    return res  }}

createFunctionalComponent 在创立函数式组件的过程中,次要做了这么几件事:

  • 设置组件的 props 对象
  • 实例化组件,获取渲染上下文。
  • 调用 render 办法,生成 VNode.
  • 返回标记后的 VNode。
归根到底,应用 createElement 来创立元素的 VNode,createComponent 来创立组件的 VNode,每个 VNode 有 childrenchildren 蕴含子 VNode ,就造成了一个 VNode Tree。_render 函数的执行就是为了取得到一颗 VNode 树。

相干链接

Vue源码解读(预):手写一个简易版Vue

Vue源码解读(一):筹备工作

Vue源码解读(二):初始化和挂载

Vue源码解读(三):响应式原理

Vue源码解读(四):更新策略

Vue源码解读(五):render和VNode

[Vue源码解读(六):update和patch(待续)]()

[Vue源码解读(七):模板编译(待续)]()

如果感觉还对付的话,给个赞吧!!!也能够来我的集体博客逛逛 https://www.mingme.net/