当前位置: 首页 > Web前端 > vue.js

Vue初始化中的Option合并——resolveConstructorOptions

时间:2023-03-31 19:46:36 vue.js

在Vue初始化中,$option属性来自mergeOptions函数:vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)它接受3一个参数,后面两个很好理解:一个是newVue时传入的参数options(options);另一个是初始化开始时定义的变量constvm:Component=this,指向Vue实例。那么这个第一个参数是什么?这个函数返回什么?resolveConstructorOptions的源码在src/core/instance/init.js的第96行:Ctor.super)constcachedSuperOptions=Ctor.superOptionsif(superOptions!==cachedSuperOptions){//超级选项已更改,//需要解析新选项。Ctor.superOptions=superOptions//检查是否有任何后期修改/附加选项(#4976)constmodifiedOptions=resolveModifiedOptions(Ctor)//更新基础扩展选项if(modifiedOptions){extend(Ctor.extendOptions,modifiedOptions)}options=Ctor.options=mergeOptions(superOptions,Ctor.extendifOptions.name)){options.components[options.name]=Ctor}}}returnoptions}这个函数接受一个参数,就是实例的构造函数,即vm。构造函数。从上面的代码我们可以知道他的实现逻辑分为两种。一种是构造函数上的super属性存在,并进行处理;如果super属性不存在,则直接返回构造函数上的选项值。超级不存在。通过前面的初始化分析我们知道,在创建newVue时,vm.constructor中是没有super属性的。此时resolveConstructorOptions就是直接返回的构造函数的option,或者通过上面的分析得知,这个构造函数的option是在initGlobalAPI中实现的:Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})Vue.options._base=Vueextend(Vue.options.components,builtInComponents)首先,我们调用基本的构造函数(也就是newVue中的一个)赋值给options中的_base属性,然后添加ASSET_TYPES中的三个属性。exportconstASSET_TYPES=['component','directive','filter']最后一步是通过extend方法将全局组件KeepAlive添加到options中的组件中。等等,还没完。在前面的初始化分析中,我们看到在platforms/web/runtime/index中,我们也会安装平台相关的指令和组件。//installplatformruntimedirectives&componentsextend(Vue.options.directives,platformDirectives)extend(Vue.options.components,platformComponents)在web端我们安装两个指令v-model,v-show和Transition,TransitionGroup两个组件。因此,当super不存在时,我们的返回选项是这样的:super存在,那么super什么时候存在呢?因为这个resolveConstructorOptions是exported的,也就是说不仅仅在init.js中使用。我们全局搜索一下resolveConstructorOptions,发现在src/core/vdom/create-component.js中也有用:children:?Array,tag?:string):VNode|数组|void{if(isUndef(Ctor)){return}constbaseCtor=context.$options._base//普通选项对象:将其转换为构造函数if(isObject(Ctor)){Ctor=baseCtor.extend(Ctor)}...//resolveconstructoroptionsincaseglobalmixinsareappliedafter//componentconstructorcreationresolveConstructorOptions(Ctor)......}也就是我们createComponent的时候,也就是构造组件的时候例如,我们还将调用此resolveConstructorOptions。这个时候函数接收到的Ctor来自于Ctor=baseCtor.extend(Ctor),这个extend是在初始化的时候挂载到Vue构造函数上的extend方法:Vue.extend=function(extendOptions:Object):Function{扩展选项=扩展选项||{}constSuper=thisconstSuperId=Super.cidconstcachedCtors=extendOptions._Ctor||(extendOptions._Ctor={})如果(cachedCtors[SuperId]){returncachedCtors[SuperId]}constname=extendOptions.name||Super.options.nameif(process.env.NODE_ENV!=='production'&&name){validateComponentName(name)}constSub=functionVueComponent(options){this._init(options)}Sub.prototype=Object.create复制代码(Super.prototype)Sub.prototype.constructor=SubSub.cid=cid++Sub.options=mergeOptions(Super.options,extendOptions)Sub['super']=Super......Sub.superOptions=Super.optionsSub.extendOptions=extendOptionsSub.sealedOptions=extend({},Sub.options)......returnSub}那么这个extend方法做了什么?其实官网上已经解释过了:使用基本的Vue构造函数创建一个“子类”参数是一个包含组件选项的对象。所以extend函数的作用就是返回一个子组件的构造函数。它通过Object.create()、原型和构造函数的结合,实现了子组件对Vue基础组件的继承。下面看一下具体过程:通过constSuper=this,将Super指向父类,也就是基本的Vue构造函数。定义了子组件的构造函数Sub。当我们new这个子组件的时候,构造函数Sub就会被执行,而他最终执行的是this._init(options)。当然这时候我们的Sub战友还是个准骨干,只有原来函数对象的属性和方法,当然不会有_init了。为了让Sub有一个_init方法,我们开始从Super继承。Sub.prototype=Object.create(Super.prototype),通过Object.create方法,将Super的原型作为自身原型的__proto__,实现原型继承。这时候我们的Sub就会拥有Vue基础构造函数的所有原型方法,包括Vue.prototype._init。当然,此时还有一个小问题。Sub.prototype.constructor也指向Super.prototype.constructor,也就是Vue的基本构造函数Vue()。这时候我们就需要通过Sub.prototype.constructor=Sub让他指向我们子类自己的构造函数VueComponent(),然后通过mergeOption等一系列操作完成子组件特有属性的挂载,以及最后返回子类结构FunctionSub。什么?为什么不继承?当然是为了避免全球污染。继承的其实是公共的属性和方法。当然,像数据这样的东西是不能通过继承来实现的。那么,我们思考的超级属性呢?其实这段代码中,Sub['super']=Super,通过它把Vue基础构造函数挂载到子类构造函数的super属性上。同时我们也通过Sub.superOptions=Super.options将Vue基类构造函数的option选项挂载到子类构造函数的superOptions属性中。既然找到了super,那么Ctor=baseCtor.extend(Ctor)的Ctor自然会有super属性,所以此时执行的resolveConstructorOptions(Ctor)会遵循另外一个逻辑:if(Ctor.super){constsuperOptions=resolveConstructorOptions(Ctor.super);constcachedSuperOptions=Ctor.superOptions;if(superOptions!==cachedSuperOptions){//超级选项已更改,//需要解析新选项。Ctor.superOptions=superOptions;//检查是否有任何后期修改/附加的选项(#4976)constmodifiedOptions=resolveModifiedOptions(Ctor);//更新基础扩展选项if(modifiedOptions){extend(Ctor.extendOptions,modifiedOptions);}options=Ctor.options=mergeOptions(.extendOptions);if(options.name){options.components[options.name]=Ctor;}}}首先调用resolveConstructorOptions方法找出父类的options,因为有可能父类也有父类,所以这里进行递归查找。它从最开始不存在的基本构造函数Vue()开始,递归,将上层父类上的最新选项更新到变量superOptions。然后将父类扩展时的上述选项,即Ctor.superOptions赋值给cachedSuperOptions变量。如果这两个变量的值不相等,说明extend之后,我们的父组件选项悄悄发生了变化,比如官网粗略的globalmix-in:那么既然父类可能混入了,我们的子类当然是可能的!我们通过resolveModifiedOptions函数来判断现有的子类选项是否与extend中子类选项中挂载的sealedOptions相同(可以查看上面extend函数的最后三段代码)。扩展后更改了子类选项。那么如果子类本身或者父类的options在extend之后发生变化,就会通过Ctor.superOptions=superOptions;和扩展(Ctor.extendOptions,modifiedOptions);superOptions和extendOptions更新后将最新的值更新为最新的选项值通过options=Ctor.options=mergeOptions(superOptions,Ctor.extendOptions);获取通过以上操作,可以保证resolveConstructorOptions返回最新的Ctor.options值。super的情况比较复杂。我们用一个简单的例子来拆解一下:letScore=Vue.extend({template:'

{{firstName}}{{lastName}}:{{score}}

'});Vue.mixin({data(){return{firstName:'Walter',lastName:'White'};}});Score.mixin({data:function(){return{score:'99'};}});newScore().$mount('#demo');首先,因为Score是Vue.extend构造的子类,所以它的Ctor有一个super属性。那么在扩展的时候,父类Vue中是没有data属性的,也就是cachedSuperOptions里面是没有数据的,但是经过Vue.mixin的操作,新得到的superOptions是有数据的。这时候因为两者不相等,就会进入if语句。并且由于socre.mixin操作,resolveModifiedOptions函数发现Socre中的options比sealedOptions有更多的数据。此时modifiedOptions不是undefined:4.最后执行options=Ctor.options=mergeOptions(superOptions,Ctor.extendOptions);,通过mergeOptions函数获取最新的options值所以,总结一下resolveConstructorOptions的作用功能:返回类构造函数上的最新选项值。