vue源码阅读之初始化过程
import阶段
从打包入口文件入手,scripts/config.js, ➡️ resolve('web/entry-runtime.js')
⤵️
入口文件 src/platforms/entry-runtime.js, platforms 文件夹是跟平台相关的代码
⤵️
src/platforms/web/runtime/index.js,runtime/index.js 里对该运行时代码做了特殊处理
⤵️
src/code/index.js,真正入口文件
import Vue from './instance/index'
// ...
initGlobalAPI(Vue)
// ...ssr相关代码
Vue.version = '__VERSION__'
export default Vue
⤵️
src/core/instance/index.js
function Vue (options) {
  // ...
  this._init(options)
}
// 添加 _init 函数
initMixin(Vue)
// 主要是添加了 $data,$props,$watch,$set,$delete 几个属性和方法
stateMixin(Vue)
// 主要是添加了 $on,$off,$once,$emit 三个方法
eventsMixin(Vue)
// 主要添加了 _update, $forceUpdate, $destroy 三个方法
lifecycleMixin(Vue)
// 主要添加了 $nextTick 和 _render 两个方法以及一大堆renderHelpers
renderMixin(Vue)
export default Vue
instance/index.js 作用是按功能模块往 Vue 原型和自身添加了许多属性和方法。
再回到 initGlobalAPI(Vue).
// 一般使用的是实例里的方法
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.options = Object.create(null)
// 循环出来的结果其实是三个 components,directives, filters
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
// builtInComponents 仅为内置组件 KeepAlive
extend(Vue.options.components, builtInComponents)
initUse(Vue) // 添加 Vue.use
initMixin(Vue) // 添加 Vue.mixin
initExtend(Vue) // 添加 Vue.extend
// 添加 Vue.component, Vue.directive, Vue.filter 方法
initAssetRegisters(Vue)
initGlobalAPI 作用也是继续添加了一些全局方法。
现在 function Vue 大致就变成了如下样子:
//构造函数
function Vue () {
  this._init()
}
//全局config对象,我们几乎不会用到
Vue.config = {
keyCodes,
_lifecycleHooks: [‘beforeCreate’, ‘created’, …]
}
// 默认的options配置,我们每个组件都会继承这个配置。
Vue.options = {
beforeCreate, // 比如 vue-router 就会注册这个回调,因此会每一个组件继承
components, // 前面提到了,默认组件有三个 KeepAlive,transition, transitionGroup,这里注册的组件就是全局组件,因为任何一个组件中不用声明就能用了。所以全局组件的原理就是这么简单
directives, // 默认只有 v-show 和 v-model
filters
}
//一些全局方法
Vue.use // 注册插件
Vue.component // 注册组件
Vue.directive // 注册指令
Vue.nextTick //下一个tick执行函数
Vue.set/delete // 数据的修改操作
Vue.mixin // 混入mixin用的
//Vue.prototype 上有几种不同作用的方法
//由initMixin 添加的 _init 方法,是Vue实例初始化的入口方法,会调用其他的功能初始话函数
Vue.prototype._init
// 由 initState 添加的三个用来进行数据操作的方法
Vue.prototype.$data
Vue.prototype.$props
Vue.prototype.$watch
Vue.prototype.$set
Vue.prototype.$delete
// 由initEvents添加的事件方法
Vue.prototype.$on
Vue.prototype.$off
Vue.prototype.$one
Vue.prototype.$emit
// 由 lifecycle添加的生命周期相关的方法
Vue.prototype._update
Vue.prototype.$forceUpdate
Vue.prototype.$destroy
//在 platform 中添加的生命周期方法
Vue.prototype.$mount
// 由renderMixin添加的$nextTick 和 _render 以及一堆renderHelper
Vue.prototype.$nextTick
Vue.prototype._render
Vue.prototype._b
Vue.prototype._e
//…
实例化阶段[new Vue({…})]
代码在 src/core/instance/init.js 里。
主要功能代码:
- 生成自增的唯一ID标识 vm._uid = uid++
- 合并 vm.constructor和传入的options,生成vm.$options- 这一步使得可以在子组件能够使用全局的 directives、filters 等方法
 
- 挂载自身 vm._self = vm
- 初始化生命周期、事件、渲染等相关钩子工作
- initLifecycle(vm)
- 定位第一个非抽象父级,并添加到 $children 里,parent.$children.push(vm)
- 添加很多变量,主要为 $parent、$children,$refs 也在这里定义了,其他 _开头的变量均为生命周期不同阶段状态的 flag- 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
 
 
- 定位第一个非抽象父级,并添加到 $children 里,
- initEvents(vm)
- 
注册的是父组件事件 
 
- 
- initRender(vm)
- 做 render 的准备工作,并未开始 render,如创建 vm._vnode,vm.$createElement,$attrs 和 $listeners
 
- callHook(vm, ‘beforeCreate’)
- 调用 beforeCreate生命周期钩子
- 
callHook 里定义了: if (vm._hasHookEvent) {vm.$emit('hook:' + hook)},所以有个小技巧实现在在父组件通过hook钩子,监听子组件生命周期方法:
- 
<Chind-Component @hook:updated="doSomething" />
 
- 调用 
- initInjections(vm) // resolve injections before data/props
- initState(vm)
- data,- props,- computed等都是在这里初始化的,常见的面试考点比如- Vue是如何实现数据响应化的答案就在这个函数中寻找
 
- initProvide(vm) // resolve provide after data/props
- callHook(vm, ‘created’)
- 调用 created生命周期钩子
 
- 调用 
 
- initLifecycle(vm)
- 存在 el ,则调用 $mount,vm.$mount(vm.$options.el)- 当然,el 也可以不写,而是在实例化的时候直接调用: new Vue({...}).$mount('#app')
 
- 当然,el 也可以不写,而是在实例化的时候直接调用: 
