Vue2能通过this访问各种选项中属性的原因是什么


今天小编给大家分享一下Vue2能通过this访问各种选项中属性的原因是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。万事开头难,找到起点是最难的,对于前端项目,我们想要找到入口文件,一般都是从package.json中的main字段开始找;package.json中的main字段代表的是这个包的入口文件,通常我们可以通过这个字段的值来找到我们要阅读的起点。但是对于Vue来说,这个字段是dist/vue.runtime.common.js,这个文件是编译后的文件,我们是看不懂的,所以需要找到源码的入口文件;这个时候我们就需要看package.json中的scripts字段:

{
"scripts":{
"dev":"rollup-w-cscripts/config.js--environmentTARGET:full-dev",
"dev:cjs":"rollup-w-cscripts/config.js--environmentTARGET:runtime-cjs-dev",
"dev:esm":"rollup-w-cscripts/config.js--environmentTARGET:runtime-esm",
"dev:ssr":"rollup-w-cscripts/config.js--environmentTARGET:server-renderer",
"dev:compiler":"rollup-w-cscripts/config.js--environmentTARGET:compiler",
"build":"nodescripts/build.js",
"build:ssr":"npmrunbuild--runtime-cjs,server-renderer",
"build:types":"rimraftemp&&tsc--declaration--emitDeclarationOnly--outDirtemp&&api-extractorrun&&api-extractorrun-cpackages/compiler-sfc/api-extractor.json",
"test":"npmrunts-check&&npmruntest:types&&npmruntest:unit&&npmruntest:e2e&&npmruntest:ssr&&npmruntest:sfc",
"test:unit":"vitestruntest/unit",
"test:ssr":"npmrunbuild:ssr&&vitestrunserver-renderer",
"test:sfc":"vitestruncompiler-sfc",
"test:e2e":"npmrunbuild--full-prod,server-renderer-basic&&vitestruntest/e2e",
"test:transition":"karmastarttest/transition/karma.conf.js",
"test:types":"npmrunbuild:types&&tsc-p./types/tsconfig.json",
"format":"prettier--write--parsertypescript"(src|test|packages|types)/**/*.ts"",
"ts-check":"tsc-ptsconfig.json--noEmit",
"ts-check:test":"tsc-ptest/tsconfig.json--noEmit",
"bench:ssr":"npmrunbuild:ssr&&nodebenchmarks/ssr/renderToString.js&&nodebenchmarks/ssr/renderToStream.js",
"release":"nodescripts/release.js",
"changelog":"conventional-changelog-pangular-iCHANGELOG.md-s"
}
}

可以看到Vuepackage.json中有很多的scripts,这些相信大家都可以看得懂,这里我们只关注devbuild这两个脚本;dev脚本是用来开发的,build脚本是用来打包的,我们可以看到dev脚本中有一个TARGET的环境变量,这个环境变量的值是full-dev,我们可以在scripts/config.js中找到这个值;直接在scripts/config.js搜索full-dev:这样就可以找到这个值对应的配置:

varconfig={
'full-dev':{
entry:resolve('web/entry-runtime-with-compiler.ts'),
dest:resolve('dist/vue.js'),
format:'umd',
env:'development',
alias:{he:'./entity-decoder'},
banner
}
}

entry字段就是我们要找的入口文件,这个文件就是Vue的源码入口文件,后面的值是web/entry-runtime-with-compiler.ts,我们可以在web目录下找到这个文件;但是并没有在根目录下找到web目录,这个时候我们就大胆猜测,是不是有别名配置,这个时候我也正好在scripts下看到了一个alias.js文件,打开这个文件,发现里面有一个web的别名;代码如下:

module.exports={
vue:resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler:resolve('src/compiler'),
core:resolve('src/core'),
web:resolve('src/platforms/web'),
weex:resolve('src/platforms/weex'),
shared:resolve('src/shared')
}

为了验证我们的猜测,我们可以在config.js中搜一下alias,发现确实有引入这个文件:

constaliases=require('./alias')
constresolve=p=>{
constbase=p.split('/')[0]
if(aliases[base]){
returnpath.resolve(aliases[base],p.slice(base.length+1))
}else{
returnpath.resolve(__dirname,'../',p)
}
}

再搜一下aliases,发现确实有配置别名:

//省略部分代码
constconfig={
plugins:[
alias({
entries:Object.assign({},aliases,opts.alias)
}),
].concat(opts.plugins||[]),
}

这样我们就可以确认,web就是src/platforms/web这个目录,我们可以在这个目录下找到entry-runtime-with-compiler.ts这个文件;这样我们就成功的找到了Vue的源码入口文件,接下来我们就可以开始阅读源码了;上面找到了入口文件,但是还是不知道如何阅读源码,这个时候我们就需要一些技巧了,这里我就分享一下我自己的阅读源码的技巧;像我们现在看的源码几乎都是使用esm模块化或者commonjs模块化的,这些都会有一个export或者module.exports,我们可以通过这个来看导出了什么;只看导出的内容,其他的暂时不用管,直接找到最终导出的内容,例如Vue的源码:entry-runtime-with-compiler.ts的导出内容:

importVuefrom'./runtime-with-compiler'

exportdefaultVue

这个时候就去找runtime-with-compiler.ts的导出内容:runtime-with-compiler.ts的导出内容:

importVuefrom'./runtime/index'

exportdefaultVueasGlobalAPI

这个时候就去找runtime/index.ts的导出内容:runtime/index.ts的导出内容:

importVuefrom'core/index'

exportdefaultVue

这个时候就去找core/index.ts的导出内容:core/index.ts的导出内容:

importVuefrom'./instance/index'

exportdefaultVue

这个时候就去找instance/index.ts的导出内容:instance/index.ts的导出内容:

functionVue(options){
if(__DEV__&&!(thisinstanceofVue)){
warn('Vueisaconstructorandshouldbecalledwiththe`new`keyword')
}
this._init(options)
}

exportdefaultVueasunknownasGlobalAPI

这样我们就找到Vue的构造函数了,这个时候我们就可以开始阅读源码了;阅读源码的目的一定要清晰,当然你可以说目的就是了解Vue的实现原理,但是这个目的太宽泛了,我们可以把目的细化一下,例如:Vue的生命周期是怎么实现的Vue数据响应式是怎么实现的Vue的模板编译是怎么实现的Vue的组件化是怎么实现的Vue的插槽是怎么实现的等等…例如我们的这次阅读计划就是了解Vuethis为什么可以访问到选项中的各种属性,这里再细分为:Vuethis是怎么访问到dataVuethis是怎么访问到methodsVuethis是怎么访问到computedVuethis是怎么访问到props的上面顺序不分先后,但是答案一定是在源码中。上面已经找到了Vue的入口文件,接下来我们就可以开始阅读源码了,这里我就以Vuethis为什么可以访问到选项中的各种属性为例,来分析Vue的源码;首先看一下instance/index.ts的源码:

import{initMixin}from'./init'
import{stateMixin}from'./state'
import{renderMixin}from'./render'
import{eventsMixin}from'./events'
import{lifecycleMixin}from'./lifecycle'
import{warn}from'../util/index'
importtype{GlobalAPI}from'types/global-api'

functionVue(options){
if(__DEV__&&!(thisinstanceofVue)){
warn('Vueisaconstructorandshouldbecalledwiththe`new`keyword')
}
this._init(options)
}

//@ts-expect-errorVuehasfunctiontype
initMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
stateMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
eventsMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
lifecycleMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
renderMixin(Vue)

exportdefaultVueasunknownasGlobalAPI

有这么多东西,我们不用管,要清晰目的,我们在使用Vue的时候,通常是下面这样的:

constvm=newVue({
data(){
return{
msg:'helloworld'
}
},
methods:{
say(){
console.log(this.msg)
}
}
});

vm.say();

也就是Vue的构造函数接收一个选项对象,这个选项对象中有datamethods;我们要知道Vuethis为什么可以访问到datamethods,那么我们就要找到Vue的构造函数中是怎么把datamethods挂载到this上的;很明显构造函数只做了一件事,就是调用了this._init(options)

this._init(options)

那么我们就去找_init方法,这个方法在哪我们不知道,但是继续分析源码,我们可以看到下面会执行很多xxxMixin的函数,并且Vue作为参数传入:

//@ts-expect-errorVuehasfunctiontype
initMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
stateMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
eventsMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
lifecycleMixin(Vue)
//@ts-expect-errorVuehasfunctiontype
renderMixin(Vue)

盲猜一波,见名知意:initMixin:初始化混入stateMixin:状态混入eventsMixin:事件混入lifecycleMixin:生命周期混入renderMixin:渲染混入我们就去找这些混入的方法,一个一个的找,找到initMixin,直接就找了_init方法:

exportfunctioninitMixin(Vue:typeofComponent){
Vue.prototype._init=function(options?:Record){
constvm:Component=this
//auid
vm._uid=uid++

letstartTag,endTag
/*istanbulignoreif*/
if(__DEV__&&config.performance&&mark){
startTag=`vue-perf-start:${vm._uid}`
endTag=`vue-perf-end:${vm._uid}`
mark(startTag)
}

//aflagtomarkthisasaVueinstancewithouthavingtodoinstanceof
//check
vm._isVue=true
//avoidinstancesfrombeingobserved
vm.__v_skip=true
//effectscope
vm._scope=newEffectScope(true/*detached*/)
vm._scope._vm=true
//mergeoptions
if(options&&options._isComponent){
//optimizeinternalcomponentinstantiation
//sincedynamicoptionsmergingisprettyslow,andnoneofthe
//internalcomponentoptionsneedsspecialtreatment.
initInternalComponent(vm,optionsasany)
}else{
vm.$options=mergeOptions(
resolveConstructorOptions(vm.constructorasany),
options||{},
vm
)
}
/*istanbulignoreelse*/
if(__DEV__){
initProxy(vm)
}else{
vm._renderProxy=vm
}
//exposerealself
vm._self=vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm,'beforeCreate',undefined,false/*setContext*/)
initInjections(vm)//resolveinjectionsbeforedata/props
initState(vm)
initProvide(vm)//resolveprovideafterdata/props
callHook(vm,'created')

/*istanbulignoreif*/
if(__DEV__&&config.performance&&mark){
vm._name=formatComponentName(vm,false)
mark(endTag)
measure(`vue${vm._name}init`,startTag,endTag)
}

if(vm.$options.el){
vm.$mount(vm.$options.el)
}
}
}

代码这么多没必要全都看,记住我们的目的是找到datamethods是怎么挂载到this上的;先简化代码,不看没有意义的代码:

exportfunctioninitMixin(Vue){
Vue.prototype._init=function(options){
constvm=this
}
}

传递过来的Vue并没有做太多事情,只是把_init方法挂载到了Vue.prototype上;在_init方法中,vm被赋值为this,这里的this就是Vue的实例,也就是我们的vm;继续往下看,我们有目的的看代码,只需要看有vmoptions组合出现的代码,于是就看到了:

if(options&&options._isComponent){
initInternalComponent(vm,options)
}else{
vm.$options=mergeOptions(
resolveConstructorOptions(vm.constructor),
options||{},
vm
)
}

_isComponent前面带有_,说明是私有属性,我们通过new Vue创建的实例时走到现在是没有这个属性的,所以走到else分支;resolveConstructorOptions(vm.constructor)中没有传递options,所以不看这个方法,直接看mergeOptions

exportfunctionmergeOptions(parent,child,vm){
if(__DEV__){
checkComponents(child)
}

if(isFunction(child)){
//@ts-expect-error
child=child.options
}

normalizeProps(child,vm)
normalizeInject(child,vm)
normalizeDirectives(child)

//Applyextendsandmixinsonthechildoptions,
//butonlyifitisarawoptionsobjectthatisn't
//theresultofanothermergeOptionscall.
//Onlymergedoptionshasthe_baseproperty.
if(!child._base){
if(child.extends){
parent=mergeOptions(parent,child.extends,vm)
}
if(child.mixins){
for(leti=0,l=child.mixins.length;i

记住我们的目的,只需要关心vmoptions组合出现的代码,child就是optionsvm就是vm,简化之后:

exportfunctionmergeOptions(parent,child,vm){

normalizeProps(child,vm)
normalizeInject(child,vm)
normalizeDirectives(child)

returnoptions
}

可以看到只剩下了normalizePropsnormalizeInjectnormalizeDirectives这三个方法,值得我们关注,但是见名知意,这三个方法可能并不是我们想要的,跟进去看一眼也确实不是;虽然没有得到我们想要的,但是从这里我们也得到了一个重要信息,mergeOptions最后会返回一个options对象,这个对象就是我们的options,最后被vm.$options接收;

vm.$options=mergeOptions(
resolveConstructorOptions(vm.constructor),
options||{},
vm
)

现在我们分析要多一步了,参数只有vm的函数也是需要引起我们的注意的,继续往下看:

if(__DEV__){
initProxy(vm)
}else{
vm._renderProxy=vm
}

操作了vm,但是内部没有操作$options,跳过,继续往下看:

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm,'beforeCreate',undefined,false/*setContext*/)
initInjections(vm)//resolveinjectionsbeforedata/props
initState(vm)
initProvide(vm)//resolveprovideafterdata/props
callHook(vm,'created')

initLifecycleinitEventsinitRenderinitInjectionsinitStateinitProvide这些方法都是操作vm的;盲猜一波:initLifecycle:初始化生命周期initEvents:初始化事件initRender:初始化渲染initInjections:初始化注入initState:初始化状态initProvide:初始化依赖注入callHook:调用钩子这里面最有可能是我们想要的是initState,跟进去看一下:

exportfunctioninitState(vm){
constopts=vm.$options
if(opts.props)initProps(vm,opts.props)

//CompositionAPI
initSetup(vm)

if(opts.methods)initMethods(vm,opts.methods)
if(opts.data){
initData(vm)
}else{
constob=observe((vm._data={}))
ob&&ob.vmCount++
}
if(opts.computed)initComputed(vm,opts.computed)
if(opts.watch&&opts.watch!==nativeWatch){
initWatch(vm,opts.watch)
}
}

已经找到我们想要的了,现在开始正式分析initState。根据代码结构可以看到,initState主要做了以下几件事:初始化props初始化setup初始化methods初始化data初始化computed初始化watch我们可以用this来访问的属性是propsmethodsdatacomputed;看到这里也明白了,为什么在props中定义了一个属性,在datamethodscomputed中就不能再定义了,因为props是最先初始化的,后面的也是同理。initProps的作用是初始化props,跟进去看一下:

functioninitProps(vm,propsOptions){
constpropsData=vm.$options.propsData||{}
constprops=(vm._props=shallowReactive({}))
//cachepropkeyssothatfuturepropsupdatescaniterateusingArray
//insteadofdynamicobjectkeyenumeration.
constkeys=(vm.$options._propKeys=[])
constisRoot=!vm.$parent
//rootinstancepropsshouldbeconverted
if(!isRoot){
toggleObserving(false)
}
for(constkeyinpropsOptions){
keys.push(key)
constvalue=validateProp(key,propsOptions,propsData,vm)
/*istanbulignoreelse*/
if(__DEV__){
consthyphenatedKey=hyphenate(key)
if(
isReservedAttribute(hyphenatedKey)||
config.isReservedAttr免费云主机域名(hyphenatedKey)
){
warn(
`"${hyphenatedKey}"isareservedattributeandcannotbeusedascomponentprop.`,
vm
)
}
defineReactive(props,key,value,()=>{
if(!isRoot&&!isUpdatingChildComponent){
warn(
`Avoidmutatingapropdirectlysincethevaluewillbe`+
`overwrittenwhenevertheparentcomponentre-renders.`+
`Instead,useadataorcomputedpropertybasedontheprop's`+
`value.Propbeingmutated:"${key}"`,
vm
)
}
})
}else{
defineReactive(props,key,value)
}
//staticpropsarealreadyproxiedonthecomponent'sprototype
//duringVue.extend().Weonlyneedtoproxypropsdefinedat
//instantiationhere.
if(!(keyinvm)){
proxy(vm,`_props`,key)
}
}
toggleObserving(true)
}

代码很多,我们依然不用关心其他的代码,只关心props是怎么挂载到vm上的,根据我上面的方法,简化后的代码如下:

functioninitProps(vm,propsOptions){
vm._props=shallowReactive({})

for(constkeyinpropsOptions){
constvalue=validateProp(key,propsOptions,propsData,vm)

if(!(keyinvm)){
proxy(vm,`_props`,key)
}
}
}

这里真正有关的就两个地方:validateProp:看名字就知道是验证props,跳过proxy:代理,很可疑,跟进去看一下:

exportfunctionproxy(target,sourceKey,key){
sharedPropertyDefinition.get=functionproxyGetter(){
returnthis[sourceKey][key]
}
sharedPropertyDefinition.set=functionproxySetter(val){
this[sourceKey][key]=val
}
Object.defineProperty(target,key,sharedPropertyDefinition)
}

这里的target就是vmsourceKey就是_propskey就是props的属性名;这里通过Object.definePropertyvm的属性代理到_props上,这样就可以通过this访问到props了。不是很好理解,那我们来自己就用这些代码实现一下:

varoptions={
props:{
name:{
type:String,
default:'defaultname'
}
}
}

functionVue(options){
constvm=this
initProps(vm,options.props)
}

functioninitProps(vm,propsOptions){
vm._props={}
for(constkeyinpropsOptions){
proxy(vm,`_props`,key)
}
}

functionproxy(target,sourceKey,key){
Object.defineProperty(target,key,{
get(){
returnthis[sourceKey][key]
},
set(val){
this[sourceKey][key]=val
}
})
}

constvm=newVue(options)
console.log(vm.name);
console.log(vm._props.name);

vm.name='name'

console.log(vm.name);
console.log(vm._props.name);

上面的代码只是为了方便理解,所以会忽略一些细节,比如props的验证等等,真实挂载在_props上的props是通过defineReactive实现的,我这里直接是空的,这些超出了本文的范围。initMethods的代码如下:

functioninitMethods(vm,methods){
constprops=vm.$options.props
for(constkeyinmethods){
if(__DEV__){
if(typeofmethods[key]!=='function'){
warn(
`Method"${key}"hastype"${typeofmethods[
key
]}"inthecomponentdefinition.`+
`Didyoureferencethefunctioncorrectly?`,
vm
)
}
if(props&&hasOwn(props,key)){
warn(`Method"${key}"hasalreadybeendefinedasaprop.`,vm)
}
if(keyinvm&&isReserved(key)){
warn(
`Method"${key}"conflictswithanexistingVueinstancemethod.`+
`Avoiddefiningcomponentmethodsthatstartwith_or$.`
)
}
}
vm[key]=typeofmethods[key]!=='function'?noop:bind(methods[key],vm)
}
}

跟着之前的思路,我们忽略无关代码,简化后的代码如下:

functioninitMethods(vm,methods){
for(constkeyinmethods){
vm[key]=typeofmethods[key]!=='function'?noop:bind(methods[key],vm)
}
}

这里的vm[key]就是methods的方法,这样就可以通过this访问到methods中定义的方法了。bind的作用是把methods中定义的函数的this指向vm,这样就可以在methods中使用this就是vm了。简单的实现一下:

varoptions={
methods:{
say(){
console.log('say');
}
}
}

functionVue(options){
constvm=this
initMethods(vm,options.methods)
}

functioninitMethods(vm,methods){
for(constkeyinmethods){
vm[key]=typeofmethods[key]!=='function'?noop:bind(methods[key],vm)
}
}

functionnoop(){}

functionpolyfillBind(fn,ctx){
functionboundFn(a){
constl=arguments.length
returnl
?l>1
?fn.apply(ctx,arguments)
:fn.call(ctx,a)
:fn.call(ctx)
}

boundFn._length=fn.length
returnboundFn
}

functionnativeBind(fn,ctx){
returnfn.bind(ctx)
}

constbind=Function.prototype.bind?nativeBind:polyfillBind

constvm=newVue(options)
vm.say()

initData的代码如下:

functioninitData(vm){
letdata=vm.$options.data
data=vm._data=isFunction(data)?getData(data,vm):data||{}
if(!isPlainObject(data)){
data={}
__DEV__&&
warn(
'datafunctionsshouldreturnanobject:n'+
'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
//proxydataoninstance
constkeys=Object.keys(data)
constprops=vm.$options.props
constmethods=vm.$options.methods
leti=keys.length
while(i--){
constkey=keys[i]
if(__DEV__){
if(methods&&hasOwn(methods,key)){
warn(`Method"${key}"hasalreadybeendefinedasadataproperty.`,vm)
}
}
if(props&&hasOwn(props,key)){
__DEV__&&
warn(
`Thedataproperty"${key}"isalreadydeclaredasaprop.`+
`Usepropdefaultvalueinstead.`,
vm
)
}elseif(!isReserved(key)){
proxy(vm,`_data`,key)
}
}
//observedata
constob=observe(data)
ob&&ob.vmCount++
}

简化之后的代码如下:

functioninitData(vm){
letdata=vm.$options.data

//proxydataoninstance
constkeys=Object.keys(data)
leti=keys.length
while(i--){
constkey=keys[i]
proxy(vm,`_data`,key)
}
}

这里的实现方式和initProps是一样的,都是通过proxydata中的属性代理到vm上。注意:initData的获取值的地方是其他的不相同,这里只做提醒,不做详细分析。initComputed的代码如下:

functioninitComputed(vm,computed){
//$flow-disable-line
constwatchers=(vm._computedWatchers=Object.create(null))
//computedpropertiesarejustgettersduringSSR
constisSSR=isServerRendering()

for(constkeyincomputed){
constuserDef=computed[key]
constgetter=isFunction(userDef)?userDef:userDef.get
if(__DEV__&&getter==null){
warn(`Getterismissingforcomputedproperty"${key}".`,vm)
}

if(!isSSR){
//createinternalwatcherforthecomputedproperty.
watchers[key]=newWatcher(
vm,
getter||noop,
noop,
computedWatcherOptions
)
}

//component-definedcomputedpropertiesarealreadydefinedonthe
//componentprototype.Weonlyneedtodefinecomputedpropertiesdefined
//atinstantiationhere.
if(!(keyinvm)){
defineComputed(vm,key,userDef)
}elseif(__DEV__){
if(keyinvm.$data){
warn(`Thecomputedproperty"${key}"isalreadydefinedindata.`,vm)
}elseif(vm.$options.props&&keyinvm.$options.props){
warn(`Thecomputedproperty"${key}"isalreadydefinedasaprop.`,vm)
}elseif(vm.$options.methods&&keyinvm.$options.methods){
warn(
`Thecomputedproperty"${key}"isalreadydefinedasamethod.`,
vm
)
}
}
}
}

简化之后的代码如下:

functioninitComputed(vm,computed){
for(constkeyincomputed){
constuserDef=computed[key]
constgetter=userDef

defineComputed(vm,key,userDef)
}
}

这里的实现主要是通过defineComputed来定义computed属性,进去瞅瞅:

exportfunctiondefineComputed(target,key,userDef){
constshouldCache=!isServerRendering()
if(isFunction(userDef)){
sharedPropertyDefinition.get=shouldCache
?createComputedGetter(key)
:createGetterInvoker(userDef)
sharedPropertyDefinition.set=noop
}else{
sharedPropertyDefinition.get=userDef.get
?shouldCache&&userDef.cache!==false
?createComputedGetter(key)
:createGetterInvoker(userDef.get)
:noop
sharedPropertyDefinition.set=userDef.set||noop
}
if(__DEV__&&sharedPropertyDefinition.set===noop){
sharedPropertyDefinition.set=function(){
warn(
`Computedproperty"${key}"wasassignedtobutithasnosetter.`,
this
)
}
}
Object.defineProperty(target,key,sharedPropertyDefinition)
}

仔细看下来,其实实现方式还是和initPropsinitData一样,都是通过Object.defineProperty来定义属性;不过里面的gettersetter是通过createComputedGettercreateGetterInvoker来创建的,这里不做过多分析。上面我们已经分析了propsmethodsdatacomputed的属性为什么可以直接通过this来访问,那么我们现在就来实现一下这个功能。上面已经简单了实现了initPropsinitMethods,而initDatainitComputed的实现方式和initProps的方式一样,所以我们直接复用就好了:

functionVue(options){
this._init(options)
}

Vue.prototype._init=function(options){
constvm=this
vm.$options=options
initState(vm)
}

functioninitState(vm){
constopts=vm.$options
if(opts.props)initProps(vm,opts.props)
if(opts.methods)initMethods(vm,opts.methods)
if(opts.data)initData(vm)
if(opts.computed)initComputed(vm,opts.computed)
}

functioninitProps(vm,propsOptions){
vm._props={}
for(constkeyinpropsOptions){
vm._props[key]=propsOptions[key].default
proxy(vm,`_props`,key)
}
}

functionproxy(target,sourceKey,key){
Object.defineProperty(target,key,{
get(){
returnthis[sourceKey][key]
},
set(val){
this[sourceKey][key]=val
}
})
}

functioninitMethods(vm,methods){
for(constkeyinmethods){
vm[key]=typeofmethods[key]!=='function'?noop:bind(methods[key],vm)
}
}

functionnoop(){}

functionpolyfillBind(fn,ctx){
functionboundFn(a){
constl=arguments.length
returnl
?l>1
?fn.apply(ctx,arguments)
:fn.call(ctx,a)
:fn.call(ctx)
}

boundFn._length=fn.length
returnboundFn
}

functionnativeBind(fn,ctx){
returnfn.bind(ctx)
}

constbind=Function.prototype.bind?nativeBind:polyfillBind

functioninitData(vm){
vm._data={}
for(constkeyinvm.$options.data){
vm._data[key]=vm.$options.data[key]
proxy(vm,`_data`,key)
}
}

functioninitComputed(vm,computed){
for(constkeyincomputed){
constuserDef=computed[key]
constgetter=userDef

defineComputed(vm,key,bind(userDef,vm))
}
}

functiondefineComputed(target,key,userDef){
Object.defineProperty(target,key,{
get(){
returnuserDef()
},
})
}

constvm=newVue({
props:{
a:{
type:String,
default:'default'
}
},
data:{
b:1
},
methods:{
c(){
console.log(this.b)
}
},
computed:{
d(){
returnthis.b+1
}
}
})

console.log('propsa:default',vm.a)
console.log('datab:1',vm.b)
vm.c()//1
console.log('computedd:2',vm.d)

注意:上面的代码对比于文章中写的示例有改动,主要是为了实现最后打印结果正确,增加了赋值操作。以上就是“Vue2能通过this访问各种选项中属性的原因是什么”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注百云主机行业资讯频道。

相关推荐: vue的v-model是什么及怎么使用

这篇文章主要介绍了vue的v-model是什么及怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇vue的v-model是什么及怎么使用文章都会有所收获,下面我们一起来看看吧。v-model 是Vue框架的一种内置的API指令…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

Like (0)
Donate 微信扫一扫 微信扫一扫
Previous 02/26 16:50
Next 02/26 16:50

相关推荐