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 也可以不写,而是在实例化的时候直接调用: