详细分析Vue的数据响应式
响应响应,就是我叫你一声你敢不敢应?如果你回应了。那么这个过程就叫响应式了。
Vue的响应式原理主要通过三个属性来实现:
- get
- set
- Object.defineProperty
我们注意来对他们进行讲解。
get
get xxx(){}
是一个用来得到当前属性值的回调函数
它的精髓在于读取对象属性值。
举例说明:
let obj = {
姓: "孙",
名: "悟空",
age: 18
};
复制代码
以上我们得到了一个obj对象,如果我们想得到它的姓名,一般我们会这样做:
let obj1 = {
姓: "孙",
名: "悟空",
姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("俺叫:" + obj1.姓名());// 俺叫:孙悟空
复制代码
由于 姓名()
是个函数,所以我们不能直接去掉它的 ( ),所以我们使用get来实现。
let obj2 = {
姓: "孙",
名: "悟空",
get 姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求二:" + obj2.姓名);
复制代码
总结:get 就是这样用的。使得函数不用加括号,你暂时可以这么理解。
set
set xxx(){}
主要用来监视当前对象属性值变化,并提供修改方法。
set的精髓就是修改属性值。
同样用代码说明:
// 需求:使得姓名可以被修改
let obj3 = {
姓: "孙",
名: "悟空",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.slice(1)
},
age: 18
};
obj3.姓名 = '猪八戒'
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
//结果为: 需求三:姓 猪,名 八戒
复制代码
总结:set 就是这样用的。用 = xxx 触发 set 函数,会改变属性值。
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
如果你定义完了一个对象,希望给他额外加属性,那么可以用这个来加。
语法:Object.defineProperty(obj, prop, descriptor)
obj
复制代码
要定义属性的对象。
prop
复制代码
要定义或修改的属性的名称或Symbal
descriptor
复制代码
要定义或修改的属性描述符。注意,他最终会变成一个值并返回。
这就是Object.defineProperty()
原理,我们现在用代码,通过set和get结合的方式进行理解:
//需求:在声明完一个空对象后,尝试增加它的属性。我们要求里面的值(value)必须大于0
let daddy= {}
daddy.x= 0 //用x来存储'n'的值
Object.defineProperty(daddy, 'n', {
get(){
return this.x
},
set(value){
if(value < 0) return //一旦value<0,data就输出默认值
this.x = value
} //这里set和get相当于为daddy的属性n进行了监控
})
daddy.n = -1
console.log(`${daddy.n} `)
//这里daddy.n返回的值是0,由于n没有满足条件,所以直接返回get所得到的默认值
daddy.n = 1
console.log(`${daddy.n}`)
//这里daddy.n返回的值是1,由于n满足条件,set成功执行。
复制代码
总结 Object.defineProperty
:
- 可以给对象添加属性value
- 可以给对象添加getter / setter
- getter / setter 用于对属性的读写进行监控
代理与监听
代理
直接上代码吧~
let Data= {}
data.x= 0
Object.defineProperty(data, 'n', {
get(){
return this.x
},
set(value){
if(value < 0) return
this.x = value
}
})
复制代码
这是上一小节的代码,其实它是有问题的。因为如果有人将data.x
的赋值直接改掉,比如重新赋值 data.x = -1
,那么 -1 这个值变成默认值进入Object.defineProperty
后,set
的监控就失效了。我们设置的value>0
的需求没有被实现。
我们需要找个中介作为 data
这个对象的代理,然后使 data.x
变成一个匿名函数,这让它的初始值就无法访问了。
let Data = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问。此时,data.x作为储存'n'的值的意义消失了。我们不用再想它了。
function proxy({data}){
const obj = {} // 我们请个代理人大哥过来,现在{}里面变成了data
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // 这里的obj就是Data的代理对象
}
复制代码
这回总没办法改了吧? 我们把data锁定了,并通过代理的形式使代码顺利运行。不过人生总是充满惊喜。
假如因为某种需要,代码变成以下模式:
let myData = {n:0}
let Data = proxy({ data:myData })
复制代码
既然data变成匿名函数无法访问,那么把数据:{n:0}
调出来赋值给对对象myData
,只要篡改myData
,还是可以使set失效。
为了防止这种情况,我们动用绝招,形成一个怎么也无法被破解的结构。这时候就需要用到监听了。
监听
上代码!
let myData = {n:0}
let Data = proxy({ data:myData})
function proxy2({data}){
let value = data.n
Object.defineProperty(data, 'n', {
get(){
return value
},
set(newValue){
if(newValue<0)return
value = newValue
}
})//这里使新加的代码,无论后面怎么被篡改,都要经过它的监听,只要不合格就会被恢复成原始的 n:0 。
const obj = {}
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0) return
data.n = value
}
})
return obj
}
复制代码
这里加入了监听代码后,只要进入这段监听代码,都会被get、set监听恢复。
new Vue( )
让我们看看vue
是怎么实现的:
如上图,vm
就是data的代理人。
假设data被篡改,这个new Vue
就会开始进行监听了。
其监听代理过程如下:
- 为data创建监听,并且将n的值用value获取,先寄放在它这里。
- 获取后,会由
Object.defineProperty
为data
创建一个新的 ‘n’属性(此处,原来的n属性被干掉了,这里的新’n’代替的原来的n)。 - get(读取)此属性,将value(即原先n的值)返回。
- set (设置)一个新值V,即后面可能被重新赋值的n(比如后面有人把data重新赋值了)。此时判断这个值合不合规。(判断方法可能是将 V 与之前get到的value进行对比)
- 如果合规就将其赋值给value,并将它丢给代理,通过代理赋值给
vm
- 不合规则调用render(data) ,对UI页面进行重新渲染,UI中的n也会重新渲染。
再拿代码举例:
const myData = {
n: 0
}
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(()=>{
myData.n += 10
},3000)
复制代码
new Vue()
的作用与前面三要素的组成作用是一样的。
这里的myData
,无论后面怎么被篡改,只要进入了new Vue
,都会被get、set
监听恢复。vm
起到的作用就是成为 myData
的代理的角色,对其属性进行代理监控,这个过程 Object.defineProperty
全程参与。