vue源码学习- Vue初始化过程
Posted ~往无前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码学习- Vue初始化过程相关的知识,希望对你有一定的参考价值。
Vue
/src/core/instance/index.js. //vue初始化,也就是vue构造函数的位置
import initMixin from './init'
//vue 构造函数
function Vue(options)
if (!(this instanceof Vue))
warn$2('Vue is a constructor and should be called with the `new` keyword');
//调用Vue.prototype._init 方法,该方法是在initMinin中定义的
this._init(options);
// 定义Vue.prototype._init方法
initMixin(Vue)
export default Vue
Vue.prototype._init
/src/core/instance/init.js
/**
*定义Vue.prototype._init 方法
*@param* Vue构造函数
*/
var uid=0
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)
/**
*每个子组件初始化时走这里,这里只做了一些性能优化
*将组件配置对象上的一些深层次属性放到vm.$options 选项中,以提高代码的执行效率
*/
initInternalComponent(vm, options)
else
/**
*初始化根组件时走这里,合并Vue的全局配置到根组件的局部配置,比如Vue.component 注册的全局组件会合并到根实例的components选项中
*至于每个子组件的选项合并则发生在两个地方:
* 1.Vue.component 方法注册的全局组件在注册时做了选项合并
* 2.component:xx方式注册的局部组件在执行编译器生成的render函数时选项合并,包括根组件中的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='handleClick'/>上注册的事件,监听者不是父组件,而是子组件本身,也就是说事件的派发和监听者都是组件本身,和父组件无关
*/
initEvents(vm)
//解析组件的插槽信息,得到vm.$slot,处理渲染函数,得到vm$createElement方法,即h函数
initRender(vm)
//调用beforeCreate 钩子函数
callHook(vm,'beforeCreate')
//初始化组件的inject配置项,得到result[key]=val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个key
initInjections(vm)
//数据响应式的重点,处理props,methods,data.computed,watch
initState(vm)
//解析组件配置项上的provide对象,将其挂载到vm._provided属性上
initProvide(vm)
//调用created钩子函数
callHook(vm,'created')
//如果发现配置项上有el选项,则自动调用 $mount 方法,也就是说有了el选项,就不需要再手动调用$mount,反之,没有el则必须手动调用$mount
if(vm.$options.el)
//调用$mount方法,进入挂载阶段
vm.$mount(vm.$options.el)
resolveConstructorOptions
/src/core/instance/init.js
/**
* 从组件构造函数中解析配置对象options,并合并基类选项
* @param * Ctor
* @returns
*/
export function resolveConstructorOptions (Ctor: Class<Component>)
//配置项目,获取Vue上面绑定的属性
let options = Ctor.options
if(Ctor.super)
//存在基类,递归解析基类构造函数的选项
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if(superOptions !== cachedSuperOptions)
//说明基类构造函数选项已经发生改变,需要重新设置
Ctor.superOptions = superOptions
//检查Ctor.superOptions上是否有任何后期修改/附件的选项
const modifiedOptions = resolveModifiedOptions(Ctor)
// 如果存在被修改或增加的选项,则合并两个选项
if(modifiedOptions)
extend(Ctor.extendOptions,modifiedOptions);
//选项合并,将合并结果赋值为Ctor.options
options = Ctor.options = mergeOptions(superOptions,Ctor.extendOptions)
if(options.name)
//该组件构造函数的name为自身的组件构造函数时自己的constructor
options.components[options.name] = Ctor
return options
resolveModifiedOptions
/src/core/instance/init.js
/**
* 解析构造函数选项中后续被修改或者增加的选项
*/
function resolveModifiedOptions (Ctor: Class<Component>): ?Object
let modified;
//构造函数选项
const lastest = Ctor.options
//密封的构造函数选项,备份
const sealed = Ctor.sealedOptions
//对比两个选项,记录不一致的选项
for(const key in latest)
if(latest[key] !== sealed[key]
if(!modified) modified=
modified[key]=latest[key]
return modified
mergeOptions
/src/core/util/options.js
/**
* 合并两个选项,出现相同配置项时,子选项会覆盖父选项的配置
*/
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
):Object
if(process.env.NODE_ENV !== 'production')
checkComponents(child)
if(typeof child === 'function')
child = child.options
// 标准化props、inject、directive选项,方便后续程序的处理
normalizeProps(child ,vm)
normalizeInject(child ,vm)
normalizeDirectives(child)
//处理原始child对象上的extends和mixins,分别执行mergeOptions.将这些继承而来的选项合并到parent
//mergeOptions 处理过的对象会含有_base属性
if(!child._base)
if(child.extends)
parent = mergeOptions(parent, child.extends, vm)
if(child.mixins)
for(let i=0,l = child.mixins.length; i < l; i++)
parent = mergeOptions(parent, child.mixins[i],vm)
const options =
let key
//遍历 父选项
for (key in parent)
mergeField(key)
//遍历子选项,如果父选项不存在该配置,则合并,否则跳过,因为父子拥有同一个属性的情况在上面处理父选项时已经处理过了,用的子选项的值;
for(key in child)
if(!hasOwn(parent,key)
mergeField(key)
function mergeField(key)
// strats = Object.create(null)
const strat = strats[key] || defaultStrat
// 值如果为childVal 存在则优先使用childVal,否则使用parentVal
options[key] = strat(parent[key],child[key],vm,key)
return options
initInjections
/src/core/instance/inject.js
/**
*初始化inject配置项
* 1.得到 result[key]=val
* 2.对结果数据进行响应式处理,代理每个key到vm实例
*/
export function initInjections (vm:Component)
//解析inject配置项,然后从祖代组件的配置中找到 配置项中每一个key对应的val,最后得到 result[key]=val的结果
const result=resolveInject(vm.$options.inject,vm)
//对 result做数据响应式处理,也有代理inject配置中每个key到vm实例的作用
//不建议在子组件去更改这些数据,因为一旦祖代组件中 注入的provide发生更改,你在组件中做的更改就会被覆盖
if(result)
toggleObserving(false)
Object.keys(result).forEach(key=>
/* istanbul ignore else */
if(process.env.NODE_ENV !== 'production')
defineReactive(vm,key,result[key],()=>
warn(
`Avoid mutating an injected value directly since the changes will be ` +`overwritten whenever the provided component re-renders. ` + `injection being mutated: "$key"`,
vm)
)
else
defineReactive(vm,key,result[key]
)
toggleObserving(true)
resolveInject
/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)
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__对象
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
//如果上一个循环未找到,则采用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
initProvide
/src/core/instance/inject.js
/**
*解析组件配置项上的provide对象,将其挂载到vm._provided属性上
*/
export function initProvide (vm: Component)
const provide = vm.$options.provide
if(provide)
vm._provided = typeof provide === 'function' ? provide.call(vm):provide
总结:
Vue的初始化过程(new Vue(options))都做了什么?
- 处理组件配置项
- 初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上(也就是将Vue构造函数原型上添加的配置项合并到vm实例化对象的身上)
- 初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到vm.$options选项中,以提高代码的执行效率
- 初始化组件实例的关系属性,比如$parent,$children,$root,$refs等
- 处理自定义事件
- 调用beforeCreate钩子函数
- 初始化组件的inject配置项,得到result[key]=val形式的配置对象,然后对该配置对象进行浅层的响应式处理(只处理了对象的第一层数据),并代理每个key到vm实例上;
- 数据响应式,处理props,methods,data,computed,watch等选项
- 解析组件配置项上的provide对象,将其挂载到vm._provided属性上
- 调用created钩子函数
- 如果发现配置项上有el选项,则自动调用$mount方法,也就是说有了el选项,就不需要再手动调用$mount方法,反之,没提供el选项则必须调用$mount
- 接下来进入挂载阶段
以上是关于vue源码学习- Vue初始化过程的主要内容,如果未能解决你的问题,请参考以下文章