Vue2源码解读(二)-new Vue

6,606 阅读6分钟

前篇

上面在主流程篇讲到了Vue函数在声明和扩展上的一些逻辑,本篇主要从Vue的初始化来讲解,来看看New Vue()到底干了什么事情。

主入口初始化

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    // 告警信息打印
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 初始化
  this._init(options)
}

上面代码就是Vue函数进行调用的主入口函数,做了两件事情,先判断了是不是New来进行初始化的,然后调用了_init方法,_init方法是在主流程篇initMixin的时候挂载到了Vue的原型对象上面。

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // 赋值,_uid,也就是虚拟dom的key
    vm._uid = uid++
    // 赋值,标识为Vue
    vm._isVue = true
    // 判断调用,_isComponent未Vue内部属性,初始化的时候会走到下面
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      // 使用合并策略,对options进行合并,并赋值给$options
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // 代理
    vm._renderProxy = vm
    // 代理
    vm._self = vm
    // 初始化生命周期,也就是简单的赋值
    initLifecycle(vm)
    // 初始化事件处理机制
    initEvents(vm)
    // 初始化渲染函数,给$createElement等赋值
    initRender(vm)
    // 调用生命周期钩子函数beforeCreate
    callHook(vm, 'beforeCreate')
    // 初始化inject,父子通信相关
    initInjections(vm) // resolve injections before data/props
    // 划重点,data、props、watch、computed、methods等全部初始化完毕
    initState(vm)
    // 初始化provide,父子通信相关
    initProvide(vm) // resolve provide after data/props
    // 调用生命周期钩子函数,created
    callHook(vm, 'created')
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

上面代码部分,首先会对传进来的options进行合并处理,mergeOptions函数接下来会讲,会对其合并规则进行说明。

  • initLifecycle会对当前Vue实例初始化一些属性:
  • initEvents会对当前实例的事件进行处理,调用主流程篇eventsMixin添加上的onon、off、onceonce、emit方法进行事件的添加:
  • initRender会对当前实例添加与渲染相关的函数,以便render的时候进行使用:
  • 接下来就调用了Vue的生命周期函数-beforeCreate
  • initInjections会对新增的inject进行处理,此处会关闭后面提到的Observe:
  • initState会对传递进来的data、props、watch、computed、methods进行init处理,具体细节代码解读会在接下来的文章进行讲解,看下图:
  • initProvide,初始化_provide属性,并对_provide进行赋值
  • 调用Vue的生命周期函数-created,前面只是对实例添加属性和方法,并没有进行构建DOM,连虚拟DOM也没有创建,所以在created构造函数里面无法操作dom
  • 判断options是否有el属性,如有则进行mount

合并规则

mergeOptions函数是对数据进行合并,接受三个参数,第一个参数为Vue构造函数上在声明篇讲到的options;第二个参数为传递进来的options,第三个参数为当前实例。 第一个参数: 第二个参数,就是下面{}里面的内容;

var vm = new Vue({
  el: '#demo',
  data() {
    return {
      message: 'hello vue',
      name: 'Draven'
    }
  }
})

第三个参数为当前实例,即vm。接下来咱们看下mergeOptions的流程

export function mergeOptions (parent: Object, child: Object, vm?: Component): Object {
  // 如果是子组件,则获取子组件的options
  if (typeof child === 'function') {
    child = child.options
  }
  // 处理child的Props,把my_prop_name改为myPropName
  normalizeProps(child, vm)
  // 处理child的inject,对inject进行封装,标记from
  normalizeInject(child, vm)
  // 处理child的directives,如为函数,则设置为bind和update时调用
  normalizeDirectives(child)
  // 如果不是最顶层的child,最顶层的parent
  if (!child._base) {
    // 对extends进行递归调用合并
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    // 对mixins进行递归调用合并
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  // 根据合并策略strats合并options
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

咱们来看看strats里面都有哪些合并的策略,然后咱们一一来分析: 上图就是目前版本所有的合并策略,看起来挺多,其实就分为几类,一一来看下。

合并规则-mergeHook

所有钩子函数的合并都是调用的mergeHook,钩子函数包括如下几种:

  • 'beforeCreate',
  • 'created',
  • 'beforeMount',
  • 'mounted',
  • 'beforeUpdate',
  • 'updated',
  • 'beforeDestroy',
  • 'destroyed',
  • 'activated',
  • 'deactivated',
  • 'errorCaptured',
  • 'serverPrefetch' 来看下mergeHook部分的代码:
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

LIFECYCLE_HOOKS是个数组,包含的就是上面列举的目前版本包含的钩子函数; mergeHook函数写的是真的骚,这么多问号,TMD!!!拿其中一个钩子函数(created,父组件的created和子组件的created)来举例,先看第一个三元表达式:

  • 如果无子created,则res为父created;
  • 如有子created,则检测父created,如果已经有父created,则在父created后面拼接上子created
  • 如有子created,无父created,则直接取子created,并把子created序列化为数组 整理如下图 现在获取到了当前要使用的res,对res进行去重操作(dedupeHooks),然后返回对应的hooks,处理结束。

合并规则-mergeAssets

使用mergeAssets规则进行合并的有components、directives、filters,共计三种。 来看看mergeAssets部分的代码:

function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    return extend(res, childVal)
  } else {
    return res
  }
}

ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

上面先是用parentVal创建了个新对象,然后判断childVal:如果有,则进行合并操作,合并到刚才创建的对象,然后返回;如果无,则直接进行返回; 流程图如下:

合并规则-mergeWatch

使用mergeWatch合并规则的就只有watch,来看看mergeWatch部分的代码,

function mergeWatch(
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  if (!childVal) return Object.create(parentVal || null)
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

上面部分代码,先是对parentVal和childVal进行了判断和赋值,然后检测了childVal; 如无childVal,返回一个新的parentVal对象;如无parentVal,返回childVal;都有则开始继承,遍历childVal,往ret里面赋值,此处所赋的值都为数组,流程图如下:

合并规则-mergeDataOrFn

使用mergeDataOrFn的合并规则的属性有data和provide两个,其中data会先进行一部分处理再调用mergeDataOrFn,先看下这部分代码:

function mergeData(
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}

对传递进来的数据进行了讨论,如果无实例对象,并且childVal有值,同时childVal不是一个函数,则直接返回parentVal. 接下来看mergeDataOrFn的代码:

function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

上面代码先是对实例进行了判断,分为两个分支,一个是无实例的话:无childVal返回parentVal,无parentVal返回childVal,都有的话,返回一个mergedDataFn函数,另外一个分支则是有实例的话,直接返回一个mergedInstanceDataFn函数。 上面返回的两个函数内部都是调用的mergeData,咱们可以直接去看mergeData函数的实现。

function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from)

  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    if (key === '__ob__') continue
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

此处的merge是相当于把两个对象进行合并,返回要合并到的对象,不是一个新建的对象,简单画了下流程图

合并规则-mergeOther

起了个简单的名字-mergeOther,使用到此规则的属性包含props、methods、inject、computed四个属性。

function mergeOther(parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ?Object {
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}

简单merge,继承市合并,返回新对象

合并规则-defaultStrat

这个更加简单,只是简单的判断返回,仅一行代码

function defaultStrat(parentVal: any, childVal: any): any {
  return childVal === undefined ? parentVal : childVal
}

至此,7个merge策略已经完成,下面是整体的流程图

整体流程图

这就是new Vue所做的事情,后面我们会梳理细节。