要理解vue中的数据代理,我们需要先理解defineProperty方法,再理解数据代理的含义,最后我们再来看vue中的数据代理
Object.defineProperty方法
最基础的用法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no,maximum-scale=1.0,minimum-scale=1.0"> <title>Title</title> </head> <body> <script> let num = 18; let person = { name:'zhouzhou', sex:'male' } // 最基础用法 // 给person对象添加一个属性age,我们可以使用Object.defineProperty方法 // 传入三个参数,第一参数为对象名,第二个要添加的属性名,第三个参数为一个对象,可以在其中定义要添加的属性的值和特性 Object.defineProperty(person,'age',{ value:'21' }) </script> </body> </html>
我们输出这个person对象发现,我们通过defineProperty添加的age属性,颜色和直接添加的属性不太相同,为淡粉色
淡粉色的含义为:不可枚举
我们知道,我们通过Object.key或者foreach可以枚举出对象中的全部属性
枚举person对象后我们发现,没有age这个属性
原因是,我们通过Object.defineProperty给person添加的元素默认是不可枚举的
如果想让其可以枚举,我们可以在传入的第三个属性(对象类型的)中添加设置
除了这一点,我们还可以设置age属性的其他特性
来看下面一个例子:
我们申明了一个变量num,并且设置person属性中age的值为num
思考一个问题:age的初始值为多少呢?
我们可以在控制台中查看
再思考一个问题:修改num的值, age的值是否会改变呢?
我们发现,age的值并不会随着num的值的改变而改变,因为age = num只执行了一次
如果我们想做到修改num,age的值就随之修改,该怎么办呢?
我们可以使用上面说的Object.defineProperty来解决这个问题
被称为get函数(getter)可以简写成下面这种形式
如果打印person对象,我们就会发现,age:(...)这是因为,如果要查看其值我们需要调用getter
如果我希望修改person.age之后,num就发生变化,我们可以使用setter
数据代理
什么是数据代理?有两个对象,通过一个对象代理另一个对象中属性的操作(读/写)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no,maximum-scale=1.0,minimum-scale=1.0"> <title>Title</title> </head> <body> <!--什么是数据代理?有两个对象,通过一个对象代理另一个对象中属性的操作(读/写)--> <script> let obj1 = { name:'' } let obj2 = {} Object.defineProperty(obj2,'x',{ //通过getter,访问obj2的x属性的时候,显示obj1的name属性 get() { return obj1.name }, //通过setter,修改obj2的x属性的时候,也修改obj1的name属性 set(v) { obj1.name = v } }) </script> </body> </html>
vue中的数据代理
还记得MVVM模型吗?其中我们的第一个m(model)部分为
通过vm,我们最后会在V(视图)中访问到data这个对象
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no,maximum-scale=1.0,minimum-scale=1.0"> <title>Title</title> </head> <body> <script src="../js/vue.js"></script> <div id="app"> 目前的数据:{{message}} </div> <script> const vm = new Vue({ el:'#app', data:{ message:'hello' } }) console.log(vm); </script> </body> </html>
首先我们观察vm这个Vue实例
我们可以发现:
- 这个vm上出现了message属性,并且多了get/set message方法
- 从这点我们可以看出message是通过defineProperty加上去的
- 我们在data中定义了message属性,并且在vm上发现了它,而且使用了defineProperty方法
那么我们就可以猜想:这里是不是使用了数据代理了呢?通过vm代理了data?
接下来我们验证我们的猜想:
- 如果修改options.data中的message,vm中message发生改变,那么证明了getter生效
- 如果修改vm中的message,options.data中的message发生改变,那么证明了setter生效
验证getter是否生效很简单:改一下data中的数据,看看vm.message是否变化就完事了
我们发现,在代码中修改options.data中数据的数据从hello->hello123,再查询vm.message值为hello123,就说明,vm中的getter方法,可以查询到options.data中的数据
再验证setter方法是否生效:思路是修改vm.message,看看options.data中的数据是否修改,为此我们要想办法查看options.data中的数据,但是我们发现我们并不能直接获取到options.data,可是options.data必然存放在某个地方,那么options.data中的数据在什么地方呢?我们可以观察到,vm中有_data这一个属性,我们猜想这是options.data存放的地方,我们需要验证一下:
我们将代码变成这种格式,定义一个data对象,在vue的配置对象中让data:data(我采用es6的简写形式,我们称这个为options.data),这么做是为了方便拿到data,如果不这么写,我们发现我们无法拿到options.data,这么写了之后,options.data=data,我们要验证vm._data?=options.data只需要验证,vm._data?=data即可
输入控制台验证:
我们发现是相等的,我们可以得出结论:vm._data = options.data
那么我们接下来就可以修改vm.message的属性,修改完成之后,我们发现vm._data.message也被修改了,那么根据我们上面的出来的结论:options.data中的message也被修改了
以上论证完成
我们结论是:vm代理了vm._data而vm._data又等于options.data
好了,那么我们上面论证了半天的这个数据代理,我们使用Vue的时候如何体现呢?
最直观的体现就是当我们在页面上使用插值语法等的时候,我们可以直接拿到options.data中的数据,而不用使用_data.message这样的形式获得,目的就是让你写代码的时候少写点字