問題:new Vue(options) 發(fā)生了什么?
1.處理組件配置項
- initInternalComponent(性能優(yōu)化挂签,減少原型鏈的動態(tài)查找)疤祭,初始化每個子組件時做了一些性能優(yōu)化,將組件配置對象上的一些深層次屬性放到 vm.$options 選項中饵婆,以提高代碼的執(zhí)行效率勺馆。
- mergeOptions (選項合并),根組件初始化時進行了選項合并侨核,將全局配置的選項合并到跟組建的局部配置上草穆。
- 源碼文件路徑 :/src/core/instance/init.js
export function initMixin(Vue: Class<Component>) {
// 初始化Vue的過程
Vue.prototype._init = function (options?: Object) {
//vue實例
const vm: Component = this;
// 每個vue實例都有一個遞增uid
vm._uid = uid++;
vm._isVue = true;
//配置組件配置項
if (options && options._isComponent) {
/*性能優(yōu)化,減少原型鏈的動態(tài)查找搓译,提高執(zhí)行效率
*每個子組件初始化時走這里悲柱,這里做了一些性能優(yōu)化
*將組件一些深層次的屬性放入vm.$option中,提高代碼效率
*/
initInternalComponent(vm, options);
} else {
/**
*初始化根組件時走這里些己,合并 Vue 的全局配置到根組件的局部配置豌鸡,比如 Vue.component 注冊的全局組件會合并到 根實例的 components 選項中
*至于每個子組件的選項合并則發(fā)生在兩個地方:
* 1、Vue.component 方法注冊的全局組件在注冊時做了選項合并
* 2轴总、{ components: { xx } } 方式注冊的局部組件在執(zhí)行編譯器生成的 render 函數(shù)時做了選項合并直颅,包括根組件中的 components 配置
* */
// 根組件選項合并,將全局配置合并到根組件的局部配置上
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {
// 設置代理怀樟,將 vm 實例上的屬性代理到 vm.renderProxy
initProxy(vm);
} else {
vm._renderProxy = vm;
}
// expose real self
vm._self = vm;
//初始化組件實例關系屬性功偿,比如 $parent、$children往堡、$root械荷、$refs 等
initLifecycle(vm);
/**
* 初始化自定義事件,這里需要注意一點虑灰,<comp @click="handelClick"> 上注冊的事件吨瞎,監(jiān)聽者不是父組件,
* 而是子組件本身,也就是說事件的派發(fā)和監(jiān)聽這都是子組件本身穆咐,和父組件無關
*
**/
initEvents(vm);
//解析組件的插槽信息颤诀,得到vm.$slot處理渲染函數(shù),得到vm.$ ceraterElement方法
initRender(vm);
//
callHook(vm, "beforeCreate");
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, "created");
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
2.初始化實例關系,比如 children崖叫、refs 等
- initLifecycle()
- 源碼文件路徑:src\core\instance\lifecycle.js
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
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
}
3.處理自定義事件
- initEvents
- 源碼文件路徑 :src\core\instance\events.js
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
- 問題思考:父組件調用子組件時子組件調用的事件是誰在監(jiān)聽
<comp @click="handleClick" />
//通過vue的初始化 this.$emit('click')會被轉為this.$on('click',function handleClick(){})
//這里的this指的是子組件拍柒,所以子組件事件調用以后是子組件自己在監(jiān)聽自己
4.調用 beforeCreate 鉤子函數(shù)
- callHook(vm, "beforeCreate")
- 源碼文件路徑:src\core\instance\lifecycle.js
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
5.初始化組件的 inject 配置項(inject 為一個高階函數(shù)可在vue.js文檔中查看具體的使用方法)
- initInjections
- 源碼文件路徑 :/src/core/instance/inject.js
/**
* 解析 inject 配置項心傀,從祖代組件的 provide 配置中找到 key 對應的值,否則用 默認值拆讯,最后得到 result[key] = val
* inject 對象肯定是以下這個結構脂男,因為在 合并 選項時對組件配置對象做了標準化處理
* @param {*} inject = {
* key: {
* from: provideKey,
* default: xx
* }
* }
*/
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
// inject 配置項的所有的 key
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
// 遍歷 key
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// 跳過 __ob__ 對象
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
// 拿到 provide 中對應的 key
const provideKey = inject[key].from
let source = vm
// 遍歷所有的祖代組件养叛,直到 根組件,找到 provide 中對應 key 的值宰翅,最后得到 result[key] = provide[provideKey]
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 如果上一個循環(huán)未找到弃甥,則采用 inject[key].default,如果沒有設置 default 值堕油,則拋出錯誤
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
6. 數(shù)據響應式的處理 props潘飘、methods、data掉缺、computed、watch
- initState
- 源碼文件路徑 :src\core\instance\state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
7.解析組件配置項上的 provide 對象戈擒,將其掛載到 vm._provided 屬性上
- initProvide()
- 源碼文件路徑 :\src\core\instance\inject.js
- 注:inject()和 provide()是組合使用的高階方法 provide負責提供inject則負責子組件注入
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
8.調用 created 鉤子函數(shù)
- callHook(vm, 'created')
- 源碼文件路徑:src\core\instance\lifecycle.js
9.如果發(fā)現(xiàn)配置項上有 el 選項眶明,則自動調用 mount 方法搜囱,反之,沒提供 el 選項則必須調用 $mount
- 源碼文件路徑:src\core\instance\init.js
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
10.接下來進入掛載階段