Vue源码解析(2)

上一篇分析道_init() 方法就是Vue调用的第一个方法,也是Vue 构造函数最核心的方法,那么通过_init(),我们会得到一个怎么样的Vue实例?这一篇分析看看。

_init定义

先,我们找到这个_init方法的定义,来自src/core/instance/init.js :

  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

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

这里大致_init() 方法做了哪些事:
(1)在 this 对象上定义了两个属性:_uid 和 _isVue;
(2)performance做性能监测
(3)合并option,定义 options._isComponent;
(4)initProxy(vm)做代理拦截;
(5)初始化如下函数:

initLifecycle(vm) //生命周期变量初始化
initEvents(vm) //事件监听初始化
initRender(vm) //初始化渲染
callHook(vm, 'beforeCreate') //回调钩子beforeCreate
initInjections(vm) //初始化注入
initState(vm) // prop/data/computed/method/watch状态初始化
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') //回调钩子created

合并options

在使用 Vue 开发项目的时候,从官方注解上看,我们基本不会使用到if (options && options._isComponent) 这个 Vue 内部分支,而是走 else 分支,也就是这段代码:

      vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
      )

这样 Vue 第一步所做的事情就来了:使用策略对象合并options。
可以发现,Vue使用 mergeOptions 来处理我们调用Vue时传入的参数选项(options),然后将返回值赋值给 this.$options (vm === this),传给 mergeOptions 方法三个参数,我们分别来看一看,首先是:resolveConstructorOptions(vm.constructor)。

export function resolveConstructorOptions (Ctor: Class) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

因为resolveConstructorOptions(vm.constructor),,这里的let options = Ctor.options相当于:let options = Vue.options。 那么Vue.options的结构是什么?
我们回忆上篇,core/index.js中的initGlobalAPI(Vue),initGlobalAPI 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI 之后,会变成什么样?
进入/ src/core/global-api/index.js

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = (obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

Vue初始化options,先循环ASSET_TYPES的值末尾加s作为键,值为null,然后加了options._base,最后还通过import builtInComponents from '../components/index' 拷贝了keepalive组件,最后得到ASSET_TYPES为:

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

分析出,Vue通过initGlobalAPI 初始化后是这个样子!列出来!

Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick
Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {},
    _base: Vue
}
Vue.use
Vue.mixin
Vue.cid = 0
Vue.extend
Vue.component = function(){}
Vue.directive = function(){}
Vue.filter = function(){}

Vue.prototype.$isServer
Vue.version = '__VERSION__'

extend方法目前理解为将第二个值复制给第一个值(浅拷贝),(keepalive是一个内容很多的重要文件,在src/core/components中,标记以后用),最下面四个方法分别加了use,mixin,extend(和上边的不是一个),并在extend中使Vue.cid=0,最后一个方法被直接调用。

编译入口运行文件entry-runtime.js

下一个就是 entry-runtime.js 文件了,entry-runtime.js 文件主要做了三件事儿:
(1) 覆盖 Vue.config 的属性,将其设置为平台特有的一些方法
(2) Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件
(3) 在 Vue.prototype 上定义 patch 和 $mount
经过 entry-runtime.js 文件之后,Vue 又会变成下面这个样子:

// 安装平台特定的utils
Vue.config.isUnknownElement = isUnknownElement
Vue.config.isReservedTag = isReservedTag
Vue.config.getTagNamespace = getTagNamespace
Vue.config.mustUseProp = mustUseProp
// 安装平台特定的 指令 和 组件
Vue.options = {
    components: {
        KeepAlive,
        Transition,
        TransitionGroup
    },
    directives: {
        model,
        show
    },
    filters: {},
    _base: Vue
}
Vue.prototype.__patch__
Vue.prototype.$mount

这里要注意的是 Vue.options 的变化。另外这里的 $mount 方法很简单:

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

首先根据是否是浏览器环境决定要不要 query(el) 获取元素,然后将 el 作为参数传递给mountComponent。
最后一个处理 Vue 的文件就是入口文件 entry-runtime-with-compiler.js 了,该文件做了两件事:
(1) 缓存来自 entry-runtime.js 文件的 $mount 函数

const mount = Vue.prototype.$mount

然后覆盖覆盖了 Vue.prototype.$mount
(2) 在 Vue 上挂载 compile

Vue.compile = compileToFunctions

compileToFunctions 函数的作用,就是将模板 template 编译为render函数。

至此,我们算是还原了 Vue 构造函数,总结一下:
(1) Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的
(2) Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的
(3) entry-runtime.js 主要是添加web平台特有的配置、组件和指令,entry-runtime-with-compiler.js 给Vue的 $mount 方法添加 compiler 编译器,支持 template。

你可能感兴趣的:(Vue源码解析(2))