Vue 理解之白话 getter/setter详解
当你把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter。Object.defineProperty是ES5中一个无法shim的特性,这也就是为什么Vue不支持IE8以及更低版本浏览器
以上摘自深入响应式原理
那么,把这些属性全部转为getter/setter具体是怎样一个过程呢?本文不深入具体,简单大致了解其过程,旨在整体把握,理解其主要思路
假设代码如下:
constvm=newVue({ el:'#app', data:{ msg:'helloworld' } })
data选项可以接收一个对象或者方法,这里以对象为例(其实最后都会转为对象)
首先,这个对象的所有键值对都会被挂载在vm._data上(此外vm._data对象上还有个__ob__key,暂时可以忽视),这样我们便能用vm._data.msg访问到数据
但是通常我们是用vm.msg这样访问数据,如何做到的呢?其实就是做了个代理,将data键值对中的vm[key]的访问都代理到vm._data[key]上
proxy(vm,`_data`,key) exportfunctionproxy(target:Object,sourceKey:string,key:string){ sharedPropertyDefinition.get=functionproxyGetter(){ returnthis[sourceKey][key] } sharedPropertyDefinition.set=functionproxySetter(val){ this[sourceKey][key]=val } Object.defineProperty(target,key,sharedPropertyDefinition) }
通常vm._data(下划线变量)用作内部程序,对外暴露的API是vm.$data,其实这两者也是一个东西,也是做了个代理,代码大概这样:
constdataDef={} dataDef.get=function(){returnthis._data} Object.defineProperty(Vue.prototype,'$data',dataDef) if(process.env.NODE_ENV!=='production'){ dataDef.set=function(){ warn( 'Avoidreplacinginstanceroot$data.'+ 'Usenesteddatapropertiesinstead.', this ) } }
简单理解就是访问vm.data.msg其实就是访问vm._data.msg。如果直接在开发环境对vm.data=xxx这样的赋值,而不是vm.$data.msg=xxx`这样的赋值,后者是没问题的)
至此,我们理解了为什么能用vm.msg、vm._data.msg以及vm.$data.msg三种方式获取/改变数据,最原始的数据是vm._data.msg,而另外两者即代理了_data的数据,vm.$data.msg即为Vue向外提供的API,一般情况下开发我们直接用vm.msg这样比较多,也方便,如果要获取整个data,程序中需要用this.$data,而不是this.data
接下来说getter/setter
将demo稍微添点东西:
constvm=newVue({ el:'#app', data:{ msg:'helloworld' }, computed:{ msg2(){ returnthis.msg+'123' } } })
msg2是依赖于msg的,当msg改变的时候,msg2的值需要自动更新,msg的改变可以在vm._data.msg的setter中监听到,但是怎么知道msg2是依赖于msg的呢?
直观地我们可以想到,遍历所有computed对象的键值对,然后进行分析,理论上似乎可行,但是我寻思着这可能需要解析AST啊,或者正则去匹配,看看是否用到了this.msg,也可能是this.$data.msg啊,还可能是this._data.msg,而且还要遍历data中的所有key,这看起来也太麻烦了吧,而且,如果程序中没有用到msg2,那不是多此一举了?
事实上,Vue初始化的时候会对vm._data的每个键值对设置getter/setter,大概代码如下:
//obj即为vm._data constkeys=Object.keys(obj) for(leti=0;iVue响应式核心就是,setter的时候会收集依赖,getter的时候会触发依赖更新
我们还是以上面的computedmsg2为例,当我们第一次去取值msg2时(注意,必须是取值行为,可以是在template,也可以是程序中),势必需要去取值this.msg,这就会触发msg的getter,此时我们就可以确定msg2依赖于msg
msg可以被哪些东西依赖呢?目前看来有三
- template模版中
- computed
- watch
我们可以打印vm._watchers查看,是一个Watcher实例数组,直接看实例的expression值,其实就是触发这个表达式的时候,会触发msg的getter
而这个表达式就对应上述的三种情况,因为msg改变的时候,这些表达式需要重新求值,所以这些依赖项都要保存起来,所以源码中定于了这个Watcher类
Awatcherparsesanexpression,collectsdependencies,andfirescallbackwhentheexpressionvaluechanges.Thisisusedforboththe$watch()apianddirectives.
watcher.deps数组表示该watcher的依赖项,值为Dep实例,可以理解成和Watcher实例的表达式有关的data数据。注意,deps数组可能是空,对于template而言,可以是template中不依赖于data,对于computed而言,可以是这个computed数据还没被获取(比如我定义了msg2,但是程序中没有用,这时deps为空,这表明我如果改变了msg,但是不需要通知到msg2,因为msg2根本没用到嘛,但是我在控制台输入vm.msg2,从而触发了msg的getter,继而进行了依赖收集,这时deps就不为空了,这表明我已经使用了msg2,下次msg更新时需要通知到msg2进行改变)
而对于watch而言,我试了下任何情况下deps都不为空,这需要进一步查看源码确认
deps数组元素是Dep实例,该实例有个subs属性,是Watcher实例数组,表示依赖于这个Dep的项目
Watcher和Dep比较难理解,可以暂时这样理解,Dep和data挂钩,每一个Dep实例就对应data的一个键值对,Watcher实例则依赖于Dep,那么有三个情况会依赖,也就是以上三种(想想是不是这样,当数据更新的时候,是不是只有这三处需要同时更新,或者同时响应)
总结下:我们会对data中所有键值对设置getter/setter,getter的时候我们会收集依赖(依赖项为上面三项,并不是任何情况下都会收集依赖,比如在钩子中打印msg,这时候就没依赖,所以源码中这里还有复杂判断),setter的时候我们会将收集的依赖项触发,从而更新数据,理解了这些,就能初步理解Vue的响应式原理
以上所述是小编给大家介绍的Vuegettersetter详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!