Vue源码学习之初始化模块init.js解析
我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是使用了JavaScript原型继承的原理,在Vue的原型上面增加属性和方法。我们继续跟着this._init(options)走,这个一点击进去就知道了是进入了init.js文件是在initMixin函数里面给Vue原型添加的_init方法。首先来从宏观看看这个init文件,可以看出主要是导出了两个函数:initMixin和resolveConstructorOptions,具体作用我们一步步来讨论。咋的一看这个文件,可能有些童鞋会看不明白函数参数括号里面写的是什么鬼,这个其实是应用了flow的类型检查,具体flow的使用这里就不介绍了,有兴趣的请移步:https://flow.org/en/
我们现在来看第一个函数initMixin,Vue实例在初始化的时候就调用了这个函数,
letuid=0 exportfunctioninitMixin(Vue:Class){ Vue.prototype._init=function(options?:Object){ constvm:Component=this //auid vm._uid=uid++ letstartTag,endTag /*istanbulignoreif*/【**注:istanbul是代码覆盖率检测工具,此注释为代码测试用**】 if(process.env.NODE_ENV!=='production'&&config.performance&&mark){ startTag=`vue-perf-init:${vm._uid}` endTag=`vue-perf-end:${vm._uid}` mark(startTag) } //aflagtoavoidthisbeingobserved vm._isVue=true //mergeoptions if(options&&options._isComponent){ //optimizeinternalcomponentinstantiation //sincedynamicoptionsmergingisprettyslow,andnoneofthe //internalcomponentoptionsneedsspecialtreatment. initInternalComponent(vm,options) }else{ vm.$options=mergeOptions( resolveConstructorOptions(vm.constructor), options||{}, vm ) } /*istanbulignoreelse*/ if(process.env.NODE_ENV!=='production'){ initProxy(vm) }else{ vm._renderProxy=vm } //exposerealself vm._self=vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm,'beforeCreate') initInjections(vm)//resolveinjectionsbeforedata/props initState(vm) initProvide(vm)//resolveprovideafterdata/props callHook(vm,'created') /*istanbulignoreif*/ if(process.env.NODE_ENV!=='production'&&config.performance&&mark){ vm._name=formatComponentName(vm,false) mark(endTag) measure(`${vm._name}init`,startTag,endTag) } if(vm.$options.el){ vm.$mount(vm.$options.el) } } }
我们本着宏观简化原则,这个函数里面前面有三个if判断工作我们可以先不细化讨论,大致第一个是用performance做性能监测,第二个合并option,第三个是做代理拦截,是ES6新特性,可参考阮一峰大神关于proxy的介绍【http://es6.ruanyifeng.com/#docs/proxy】。那么就进入了初始化函数主要点:
initLifecycle(vm)//生命周期变量初始化 initEvents(vm)//事件监听初始化 initRender(vm)//初始化渲染 callHook(vm,'beforeCreate')//回调钩子beforeCreate initInjections(vm)//初始化注入 initState(vm)//prop/data/computed/method/watch状态初始化 initProvide(vm)//resolveprovideafterdata/props callHook(vm,'created')//回调钩子created /*istanbulignoreif*/ if(process.env.NODE_ENV!=='production'&&config.performance&&mark){ vm._name=formatComponentName(vm,false) mark(endTag) measure(`${vm._name}init`,startTag,endTag) } if(vm.$options.el){ vm.$mount(vm.$options.el) }
这里来一个插曲start
V2.1.8及以前的版本】这里比较方便理解在生命周期created之后再做render,那么在created之前就无法获取DOM。这也是在有些源码解析文章里面很容易见到的分析,也是正确的
initLifecycle(vm) initEvents(vm) callHook(vm,'beforeCreate') initState(vm) callHook(vm,'created') initRender(vm)
v2.1.9及以后的版本】但到这里一开始就懵逼了很久render提到beforeCreate之前去了,那岂不是DOM在beforeCreate之前就能获取到了?显然不对了,请注意render虽然提前了,但是后面多了一个if这个if里面才获取DOM的关键,这个if在2.1.8版本之前是在render函数里面的,在2.1.9之后被提出来,然后render函数提前了,至于为何提前暂未了解,此处只是踩了一个看其他源码解析不同版本带来的坑!
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm,'beforeCreate') initState(vm) callHook(vm,'created') if(vm.$options.el){ vm.$mount(vm.$options.el) }
插曲end,继续
1.initLifecycle
functioninitLifecycle(vm:Component){ constoptions=vm.$options //locatefirstnon-abstractparent letparent=options.parent//我理解为父实例或者父组件 if(parent&&!options.abstract){//例子中没有parent,断点代码的时候自动跳过 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 }
这个函数主要是有父实例的情况下处理vm.$parent和vm.$children这俩个实例属性,我此处没有就跳过,其他的就是新增了一些实例属性
2.initEvents
functioninitEvents(vm:Component){ vm._events=Object.create(null) vm._hasHookEvent=false //initparentattachedevents constlisteners=vm.$options._parentListeners if(listeners){ updateComponentListeners(vm,listeners) } }
又新增两个属性,后面那个if条件里面是有父组件的事件时初始化,估计就是props和events父子组件通信的事件内容。
3.initRender
functioninitRender(vm:Component){ vm._vnode=null//therootofthechildtree vm._staticTrees=null constparentVnode=vm.$vnode=vm.$options._parentVnode constrenderContext=parentVnode&&parentVnode.context vm.$slots=resolveSlots(vm.$options._renderChildren,renderContext) vm.$scopedSlots=emptyObject vm._c=(a,b,c,d)=>createElement(vm,a,b,c,d,false) vm.$createElement=(a,b,c,d)=>createElement(vm,a,b,c,d,true) constparentData=parentVnode&&parentVnode.data /*istanbulignoreelse*/ if(process.env.NODE_ENV!=='production'){ defineReactive(vm,'$attrs',parentData&&parentData.attrs,()=>{ !isUpdatingChildComponent&&warn(`$attrsisreadonly.`,vm) },true) defineReactive(vm,'$listeners',vm.$options._parentListeners,()=>{ !isUpdatingChildComponent&&warn(`$listenersisreadonly.`,vm) },true) }else{ defineReactive(vm,'$attrs',parentData&&parentData.attrs,null,true) defineReactive(vm,'$listeners',vm.$options._parentListeners,null,true) } }
此函数也是初始化了节点属性信息,绑定createElement函数到实例【并未挂载】,接下来调用beforeCreate回调钩子;——TODO1:后续专题分析VUE渲染逻辑
4.initInjections
functioninitInjections(vm:Component){ constresult=resolveInject(vm.$options.inject,vm) if(result){ observerState.shouldConvert=false Object.keys(result).forEach(key=>{ /*istanbulignoreelse*/ if(process.env.NODE_ENV!=='production'){ defineReactive(vm,key,result[key],()=>{ warn( `Avoidmutatinganinjectedvaluedirectlysincethechangeswillbe`+ `overwrittenwhenevertheprovidedcomponentre-renders.`+ `injectionbeingmutated:"${key}"`, vm ) }) }else{ defineReactive(vm,key,result[key]) } }) observerState.shouldConvert=true } }
此函数也是当有inject属性时做处理,源码例子无inject断点跑暂时跳过
5.initState
functioninitState(vm:Component){ vm._watchers=[] constopts=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) } }
可以看出此处是对options传入的props/methods/data/computed/watch属性做初始化————TODO2:分析每个属性的初始化
6.initProvide
functioninitProvide(vm:Component){ constprovide=vm.$options.provide if(provide){ vm._provided=typeofprovide==='function' ?provide.call(vm) :provide } }
这个函数跟4.initInjections在同一个inject.js中,也是在传入参数有provide属性时做处理,暂时跳过,然后就到了created回调钩子,最后的vm.$mount接入TODO1;
今天initMixin到此结束,以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。