前一篇博客中,我们知道了getter,setter以及Object.defineProperty的用法。
现在我们看一看当我们写下:
new Vue({
data:{
n:0
}
})
Vue究竟对传进去的data做了什么
一. 例子
const myData = {
n: 0
};
console.log(myData); // 本节课精髓
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(() => {
myData.n += 10;
console.log(myData); // 本节课精髓
}, 3000);
声明一个myData对象,然后打印出来(先不执行new Vue),看到myData是这样的:
然后,new Vue创建一个实例,把myData的地址传给data。三秒之后把myData的n+10,再次打印此时的myData。
那么,一个对象经过了 new Vue被传给data选项之后,为什么变了呢?
二.
声明一个对象;
let data0 = {
n: 0
}
需求一:用 Object.defineProperty 定义 n
let data1={}
Object.defineProperty(data1,'n',{
value:0
})
用value指定值,意思是data1.n=0
可是这样写不是把事情搞复杂了吗?我本来可以直接定义n的
需求二:n 不能小于 0
即如果我让data.n赋值为-1,就是无效的,n还是原来的值。但是赋值为正数是有效的。
let data2 = {}
data2._n = 0 // _n 用来偷偷存储 n 的值
Object.defineProperty(data2, 'n', {
get(){
return this._n
},
set(value){
if(value < 0) return
this._n = value
}
})
让_n偷偷存储n的值,给属性n设get,set。如果读data2的n,就会返回data2的_n;如果设置n的值,先判断如果传的值小于0,就直接return,不设置。否则就把value赋给data2的_n属性。
即:读n,就是读_n;写n,就是写_n的值。在set里判断条件。
console.log(data2.n) 0
data2.n=-1
console.log(data2.n) //0
data2.n=1
console.log(data2.n) //1
这样就做到了让n的值不能小于0.
可是有杠精问:你设了data2._n,那我直接data2._n=-1 不就修改了吗?
需求三:使用代理
既然你说你能直接访问名字修改,那我不给你名字,你不就访问不到了。
let data3 = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问
function proxy({data}/* 解构赋值 */){
const obj = {}
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // obj 就是代理
}
我把有data属性的对象作为参数,传给了proxy函数。这是个匿名对象,没名字你就访问不到。
proxy的形参,意思是接受一个options,把options.data命名为data(解构赋值)。
在proxy函数里,给对象obj设置有get和set的属性n。实现:obj.n === data.n
obj.n=1 === data.n=1,然后返回这个对象obj。
调用proxy时,又赋给了data3。也就是说,data3就是obj。读data3的n,就是读data的n;设置data3的n,就是设置data的n。
所以,obj就是代理。对data3的所有操作,都会由obj原封不动的传达给data。
console.log(data3.n) 0
data3.n = -1
console.log(data3.n) //0
data3.n = 1
console.log(data3.n) //1
现在我用了匿名对象,你改不了了吧?
但是!杠精又说:我先声明一个对象,把这个对象的地址传给data。让这个对象有了名字,我改myData:
let myData = {n:0}
let data4 = proxy({ data:myData })
console.log(data4.n) //0
myData.n = -1
console.log(data4.n) //-1 什么?修改成功了???
需求五: 就算用户擅自修改 myData,也要拦截他
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 })
function proxy2({data}){ /* 解构赋值 */
let value = data.n
Object.defineProperty(data, 'n', {
get(){
return value
},
set(newValue){
if(newValue<0)return
value = newValue
}
})
// 就加了上面几句,这几句话会监听 data
const obj = {}
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return//这句话多余了
data.n = value
}
})
return obj // obj 就是代理
}
新加的一段代码做了什么?
- 先把传进来的data的n放到value身上,先保存
- 把data的n属性设get,set。由于这个n和之后传进来的myData5的n同名,所以会覆盖。也就是说,myData5的原来的n已经消失了,现在的n是被设了get,set的n。
后一段代码还是使用obj代理,目的是对data5的读写会由obj原封不动传给data,也就是myData5。
console.log(data5.n) //0
像四一样直接修改myData5的n:
myData5.n = -1
console.log(data5.n) //0 失败了!!!
myData5.n = 1
console.log(data5.n) //1
简单来说,先把传进来的data.n换成一个全新的n,这个全新的n由于设成了get,set,就有了监听功能。即使你通过myData5.n=-1 修改,你改的也不是原来的n了,而是具有监听功能的n。set会判断,如果小于0 ,也不给设置。
所以,即使myData5有名字,也不能随意修改了。
三. new Vue
let data5 = proxy2({ data:myData5 })
let vm = new Vue({data: myData})
这两句话是一个原理。所以第二句话做了这些事情:
- 让vm成为myData的代理。使用的this就是vm,vm就是myData
- 对myData的所有属性进行监控。myData身上的n也好,m也好,都会把他们一一挂到自己的value上保存一下。然后把他们换成有getter,setter的n/m。
监控的作用?对myData属性的所有改动,vm这个代理都要知道。 知道了之后,就可以render(data),渲染到页面。所以我们改this.n,都会更新到视图里。
所以一开始的问题就清楚了,一个data对象经过了new Vue之后,它里边的属性们就变成了有getter,setter的同名属性(虚拟),所以打印出来会显示(...)
Vue从来没有删掉data这个对象。是在这个对象身上做改动