Vue2.x源码学习笔记(一)——Vue初始化

170 阅读3分钟

Vue入口

Vue的入口是在src/core/instance/index.js中,它的真身就是一个Function。

在此文件中还进行了一系列的初始化操作:

  • initMinxin:在Vue原型上定义了_init方法。_init方法在new Vue()时会调用,用来初始化。

  • stateMixin:在Vue原型上定义了$data和$props两个实例属性。并定义了$del、$set、$watch实例方法。

  • eventsMinin:在Vue原型上定义了$on、$once、$off、$emit实例方法。

  • lifecycleMixin:在Vue原型上定义_update、$forceUpdate、$destroy实例方法。

  • renderMixin:在Vue原型上定义了_render、$nextTick实例方法。

src/core/global-api/index.js中,通过initGlobalAPI()方法,给 Vue 这个对象本身扩展全局的静态方法。

_init初始化

此方法在initMinxin(Vue)中挂载到Vue原型,并在new Vue()时调用。

 Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._uid = uid++
    vm._isVue = true
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      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)
    }
  }

每个vue实例都有一个_uid,并且是依次递增的。根据_isComponent判断是子组件还是根组件。对于自组件通过initInternalComponent来进行优化,减少原型链查找配置。对于根组件通过mergeOptions 处理组件配置项,这里将用户传递的options选项与当前构造函数的options属性及其父级实例构造函数的options合并生成一个新的options赋值给$options。然后设置代理,将vm实例上的属性代理到vm._renderProxy。接着进行一系列初始化:

  • initLifecycle:初始化vm.$parent、vm.$root、vm.$children、vm.$refs等属性值。

  • initEvents:初始化事件中心:vm._events = Object.create(null)。

  • initRender:初始化渲染定义vm._c(用于用户使用template模式) vm.$createElement(用于用户手写render函数)。

  • callHook(vm, 'beforeCreate'):生命周期beforeCreate 。

  • initInjections:初始化inject。

  • initState:初始化 data, props, methods, computed, watch 等等。响应式就是这里处理的。

  • initProvide:初始化provide。

  • callHook(vm, 'created'):生命周期created。

如果发现配置项上有el选项,则自动调用$mount方法,也就是说有了el选项,就不需要再手动调用$mount,反之,没有el则必须手动调用$mount。

initLifecycle

function initLifecycle (vm: Component) {
  const options = vm.$options
  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
}

如果当前组件不是抽象组件,并且$options存在parent,那么将不断向上层查找直到找到非抽象类的父级(例如keep-alive组件就是抽象类),然后赋值给$parent属性。并且将自己添加到父级的$children属性中。如果当前组件没有父级,那么它自己就是根组件,也就是$root就是它本身。如果存在父级,那么它的$root就沿用父级的$root。然后定义一系列属性的默认值。

initEvents

function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

这里初始化了事件系统,创建了一个用于存放事件的空对象。之后注册的事件都会存放于_events中。

在模板编译时,如果解析到组件标签,会实例化子组件。并将标签上注册的事件解析成object并通过参数传递给子组件。这些事件则保存在vm.$options._parentListeners中。当它不为空时则调用updateComponentListeners方法去注册事件。

updateComponentListeners

let target: any
function add (event, fn) {
  target.$on(event, fn)
}
function remove (event, fn) {
  target.$off(event, fn)
}
function createOnceHandler (event, fn) {
  const _target = target
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }
  }
}
function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

target用于存放当前实例,add用于给当前实例注册事件,remove用于解除事件。createOnceHandler用来创建一个只调用一次的函数,当执行完这个函数会立即解绑。将这三个方法,以及用于存放旧事件的参数传入到updateListeners中。

updateListeners

function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.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)
    }
  }
}

这里主要的逻辑就是:如果新事件对应的事件名是null或者是undefined那么就会抛出警告。如果新的事件不存在于老的事件对象中,那就通过add注册。如果事件名在on和oldOn中都存在,但是它们并不相同,则将旧事件回调替换成on中的回调,并且把on中的回调引用指向oldOn中对应的事件。如果老的事件不存在新的事件对象中,则通过remove解除。

normalizeEvent在这里的作用是为了解析真正的事件名,因为有修饰符的事件是这样的

<child v-on:increment.once="a"></child>
转换为{~increment:function(){}}

通过normalizeEvent会解析得到事件名increment。