vue源码解析(二)

187 阅读3分钟
先决条件
  • 需要能够熟悉使用 vue ,了解vue属性和方法。
  • 带着问题去看源码
  • 熟悉ES6或者Typescript语法
  • 确定源码的版本,我看的是 2.6.12
问题
  • vue在初始化的时候都做了什么事情?
vue的初始化

上个章节我们知道了vue源代码的入口文件src/platforms/web/entry-runtime.js

import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'

import {
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

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

第一行import Vue from 'core/index',就引入了vue的主体,并在这个主体上进行扩展。比如,添加一些平台的特定方法 vue.config.mustUseProp,不过,我们平常并不会用到。 要注意的是,在入口文件给vue绑定了原型方法 $mount,这个在后面会用到。


核心文件介绍

上面的代码,很多都引入了core文件,我们了解下 core 这个文件夹的每个文件的作用。 core core翻译过来就是 核心 的意思,vue的核心代码都在这个文件夹内。

  • components 定义组件代码
  • global-api 给vue设置全局配置项,全局API方法等,比如Vue.set Vue.nextTick
  • instance 创建vue初始化函数,实例方法,实例属性和构建vue的生命周期,这个就很重要了。
  • observer 创建vue中常用的观察者模型,我们的双向数据绑定和一些watch监听都依赖于它
  • util 创建一些工具函数提供给源码使用
  • vdom 创建vue的虚拟DOM
  • config.js 常用的配置项给源码使用
  • index.js core 的入口代码文件

我们看下 core 的入口代码,第一行 import Vue from './instance/index' 还是在引用vue主体,目前还是没有找到Vue主体。 core/index.js这个core/index.js的作用,第一是通过initGlobalAPI来初始化Vue全局的配置项和全局API(具体请看vue的文档);第二是定义Vue的原型方法 $isServer ,$ssrContext, FunctionalRenderContext


Vue主体函数

通过入口文件,我们继续向里面深挖 ,看到这里老铁们,我们找到了Vue的主体了。

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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')
  }

  // 执行 initMixin中定义的原型方法
  this._init(options)
}

// 定义初始化函数,实例方法,触发生命周期钩子函数
initMixin(Vue)

// 定义实例属性,$data, $props 和 实例方法 $set(), $delete(), $watch()
stateMixin(Vue)

// 定义实例方法/属性 $on(), $once(), $off() 和 $emit()
eventsMixin(Vue)

// 定义实例方法/生命周期 $forceUpdate(), $destory(),_update()
lifecycleMixin(Vue)

// 定义实例方法/生命周期  $nextTick(),
// 并且定义内部方法 _render()
renderMixin(Vue)

export default Vue

function Vue() {....} 就是构建Vue的主体函数。 这个文件里面有很多方法比如initMixin(), stateMixin(), eventsMixin()等 都是围绕者主体函数来构建Vue实例方法,内部方法或者添加钩子函数的,不过这都不重要。

在这里的重点是,当我们使用vue的时候 ,它执行了 function Vue() {....} 主体函数里面的 this._init() 方法。

 new Vue({
   data: {}
 })
调用了 this._init(options)

initMixin() 定义了 this._init()方法。

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._uid = uid++
    // a flag to avoid this being observed
    vm._isVue = true
   
    // 定义实例属性 $options,可以重置一些自定义property属性和方法,比如自定义 created(),methods() 和 data
    // vm.constructor 是在 initGlobalAPI 定义的
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )

    // expose real self
    vm._self = vm

    // 定义实例属性 $root, $parent, $children, $refs
    // 并初始化实例的一些内部属性 _watcher,_inactive, _isMounted 等
    initLifecycle(vm)

    // 初始化事件
    initEvents(vm)

    // 定义实例属性 $slots, $scopedSlots, $createElement, $attrs, '$listeners
    initRender(vm)

    // 触发钩子函数 beforeCreate
    callHook(vm, 'beforeCreate')

    // provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
    initInjections(vm) // resolve injections before data/props

    // 初始化状态,比如 data, props, methods, computed和watch
    initState(vm)

    // provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
    initProvide(vm) // resolve provide after data/props

    // 当数据,方法等必要属性都初始化后,触发created钩子函数
    callHook(vm, 'created')

    // 将实例挂载到Dome上
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

_init() 函数做了很多事了,初始化事件,添加了很多实例属性,触发钩子函数,初始化数据,对数据做监听,最后把实例通过$mount()挂载到dome上。$mount()函数在一开始src/platforms/web/entry-runtime.js代码文件被定义。


#####总结: 从找到编译的源代码入口src/platforms/web/entry-runtime.js,到最后找到 Vue的主体函数。vue初始化的主线都比较清晰,感觉就像拨洋葱,在核心里创建函数主体,然后给函数主体一层一层添加需要的方法和属性。下一节看vue的生命周期是如何形成的。