Vue2 初始化实例过程源码解析(二)

212 阅读2分钟

上一节我们从打包入口寻找到了初始化实例入口,并做了大致的描述,接下来,让我们看看每一步分别都做了什么

我们先从上而下阅读一下关键性代码

import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

export function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this
    vm._uid = uid++

    vm._isVue = true
    
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    
    vm._renderProxy = vm
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm)
    initState(vm)
    initProvide(vm)
    callHook(vm, 'created')

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

1. 合并选项

if (options && options._isComponent) {
  initInternalComponent(vm, options)
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}

1.1 initInternalComponent

export function initInternalComponent (vm, options) {
  // 获取实例的选项并阻断原型链
  const opts = vm.$options = Object.create(vm.constructor.options)
  
  // 将传入的选项内容合入当前组件选项下
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode
  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

当初始化子组件时会执行initInternalComponent,对子组件进行优化,会将组件配置对象的选项合并到子组件上,方便后续的计算和查找。

1.2 resolveConstructorOptions

// 从组件的构造函数中获取配置选项并合并
export function resolveConstructorOptions (Ctor) {
  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(Ctor.extendOptions, modifiedOptions)
      }
      // 合并选项并赋值给Crol.options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

1.3 resolveModifiedOptions

// 处理修改的选项
function resolveModifiedOptions (Ctor) {
  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
}

1.4 mergeOptions

export function mergeOptions (
  parent,
  child,
  vm
) {
  // 如果合并项是函数,则取其选项
  if (typeof child === 'function') {
    child = child.options
  }

  // 标准化Props,inject,derectives
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  
  // 当child是原始对象时(进行过mergeOptions处理才有_base),合并child的extends和mixins选项
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  // 使用合并策略合并parent和child
  const options = {}
  let key
  for (key in parent) {
    // 合并父选项(已经处理了子选项同key的情况)
    mergeField(key)
  }
  for (key in child) {
    // 如果父选项中不存在该值,合并子选择
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  // strats是一种合并策略,它对data,watcher,props,methods,inject,provide,computed的选项合并做了分别的处理
  // 其中watcher相对特殊一些,会将父子项都放入当前key的数组中,其他项的大致思路皆为子选项优先
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

1.5normalizeProps

// 标准化props
function normalizeProps (options, vm) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        res[name] = { type: null }
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  }
  options.props = res
}

props有数组和对象两种形式,源码中分别进行了解析。做了一下两件事情 1. 标准化 props 名称,将连字符形式转为小驼峰 2. 每一个 propskey 必须有一个 type 属性,没有则赋值为 null

标准化 injectdirectives 处理也类似,关键点在于初始化属性, inject 设置了 formdirectives 设置了 bindupdate,下面贴一下代码。

1.6 normalizeInject

function normalizeInject (options, vm) {
  const inject = options.inject
  if (!inject) return
  const normalized = options.inject = {}
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  }
}

1.7normalizeDirectives

function normalizeDirectives (options) {
  const dirs = options.directives
  if (dirs) {
    for (const key in dirs) {
      const def = dirs[key]
      if (typeof def === 'function') {
        dirs[key] = { bind: def, update: def }
      }
    }
  }
}

2. initLifecycle

export function initLifecycle (vm) {
  const options = vm.$options

  // keep-alive是一个抽象组件,不负责组件挂载,因此要往上寻找父级
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  // 注册了以下与实例生命周期有关的属性与内容
  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm
  vm.$children = []
  vm.$refs = {}
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

3. initEvents

export function initEvents (vm) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // 初始化父级的附加事件(listener)
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

3.1 updateComponentListeners

export function updateComponentListeners (vm, listeners, oldListeners) {
  target = vm
  // 其中的add, remove, createOnceHandler都是事件处理机制
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

3.2 updateListeners

export function updateListeners (on, oldOn, add, remove, createOnceHandler, vm) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    // 去除函数名称前面有可能出现的 &、~、!,分别代表passive、once、capture
    event = normalizeEvent(name)
    if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        // 创建一个可捕捉错误的函数
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        // once事件处理
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      // 添加事件绑定
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      // 重名直接替换函数
      old.fns = cur
      on[name] = old
    }
  }
  // 解除事件绑定
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

4. initRender

export function initRender (vm) {
  // 子树的根节点
  vm._vnode = null
  // v-once静态树缓存
  vm._staticTrees = null
  const options = vm.$options
  // 父树中的占位节点
  const parentVnode = vm.$vnode = options._parentVnode
  const renderContext = parentVnode && parentVnode.context
  // 解析_renderChildren
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  // 设置为空对象
  vm.$scopedSlots = emptyObject
  
  // 节点渲染函数,$createElement等同于我们熟知的h函数,和_c的区别为是否要做标准化处理(外部写的渲染函数和编译后的不太一样,需要做标准化),这个我们之后再做讨论
  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)

  const parentData = parentVnode && parentVnode.data
  // 对$attrs和$listeners做响应式处理
  defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
  defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}

4.1 resolveSlots

export function resolveSlots (children, context) {
  if (!children || !children.length) {
    return {}
  }
  const slots = {}
  for (let i = 0, l = children.length; i < l; i++) {
    const child = children[i]
    const data = child.data
    // 如果节点解析有slot属性,删除该属性
    if (data && data.attrs && data.attrs.slot) {
      delete data.attrs.slot
    }
    
    // 只有处于同一上下文时,才考虑命名插槽
    if ((child.context === context || child.fnContext === context) && data && data.slot != null) {
      const name = data.slot
      const slot = (slots[name] || (slots[name] = []))
      // 如果插槽的字节点标签是template,传入它的children
      if (child.tag === 'template') {
        slot.push.apply(slot, child.children || [])
      } else {
        slot.push(child)
      }
    } else {
      (slots.default || (slots.default = [])).push(child)
    }
  }
  
  // 空、注释和异步
  for (const name in slots) {
    if (slots[name].every(isWhitespace)) {
      delete slots[name]
    }
  }
  return slots
}

本篇讲述了合并选项,以及初始化生命周期,事件选项和渲染相关内容的大致过程以及思路,下一篇会继续讲述初始化inject,provide以及相当重要的state处理