Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。
一:getter和setter
getter:
let obj1 = {
姓:"李",
名:"非非",
姓名(){
return this.姓 + this.名;
}}
console.log("打印一:" + obj1.姓名());
// "打印一: 李非非
加了get
let obj2 = {
姓:"李",
名:"非非",
get 姓名(){
return this.姓 + this.名;
}}
console.log("打印二:" + obj2.姓名); // 这里没有加括号了
//打印二:李非非
总结:getter就是一个不加括号的函数
setter:
let obj3 = {
姓:"李",
名:"霏霏",
get 姓名(){
return this.姓 + this.名;
}
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.substring(1)
}}
obj3.姓名 = "刘诗诗"
console.log(`打印三: 姓${obj3.姓},名${obj3.名}`) //打印三:姓:刘,名:诗诗
总结:setter就是接受一个参数的函数 用 = xxx 这样的形式触发,来改变里面的值
二:Object.defineProperty(obj, prop, descriptor)
- obj:要在其上定义属性的对象。
- prop:Symbol要定义或修改的属性的名称或名称。
- descriptor:定义或修改的属性的描述符。
我们在使用set和get的时候都是在声明的时候就直接使用的,但是我想声明之后再使用怎么办,这就用到Object.defineProperty()了
var _xxx = 0
Object.defineProperty(obj3,"xxx",{
get(){
return _xxx
},
set(value){
_xxx = value
}
})
注意
这里定义的xxx是不存在的所以你用get传是不行的,而且会死循环(因为你在调用xxx的时候就会触发,无限循环),所以需要重新定义一个值用来传。
但是这里有一个问题,我们可以通过直接改变全局变量_xxx来改变里面的值,这不是我们想看到的,那么如何解决这个问题呢?
那就需要一个代理了
let data1 = proxy({data:{n:0}}) // 括号里面是匿名对象,所以根本无法访问
function proxy({data /*这里用了解构赋值*/}){
conost obj = {}
Object.defineProperty(obj,"n",{
get(){
return data.n
}
set(value){
if(value<0)return
data.n = value
}
})
return obj //这就是代理
}
这样你动我obj的n我就会去设置data的n,obj就是一个代理,这样你就不能擅自修改我的对象数据,但是,如果我把函数赋值给对象
let myData5 = {n:0}
let data1 = proxy({data:myData5})
那我只要修改myData5 还是能改你的数据,这时候就不能只靠代理了,还需要监听,就算你改了也没用,我监听你
let myData5 = {n:0}
let data = proxy({data:myData5})
function proxy({data /*这里用了解构赋值*/}){
let value = data.n
dalate data.n //这一句可以不写因为下面已经覆盖了原先的n
Object.defineProperty(data,"n",{ //这里挂载的对象就是参数data
get(){
return value
}
set(newvalue){
if(newvalue<0)return
value = newvalue
}
})
//上面这几句,就会监听data
conost obj = {}
Object.defineProperty(obj,"n",{
get(){
return data.n
}
set(value){
if(value<0)return
data.n = value
}
})
return obj //这就是代理
}
上面的方法就是把原先的数据复制一遍,再删掉用新数据来填补,这样你就不能通过对象修改我的数据
我上面的例子大概就是Vue内部的源代码,也许是,也许不是····
我们来看一下 Vue 到底对 data 做了什么
const Vue = window.Vue
const myData = {
n:0
}
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount('#app')
setTimeout(() => {
myData.n += 10
}, 0)
console.log(myData)
在myData 传给 Vue 的时候 ,数值就会发生改变
控制台:
{__ob__: we}
n: (...)
__ob__: we {value: {...}, dep: ce, vmCount: 1}
get n: f ()
set n: f (t)
__proto__: Object
这就是响应式,vue监听了这个数值,vue 会让 vm 成为 myData 的代理,vue 会对 myData 的所有属性进行监控,当数值发生改变的时候,vue就重新渲染
而原理是把一个普通的 JavaScript 对象传入 Vue 实例作为data选项,Vue 将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter。Object.defineProperty是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。
小结:
Vue会让vm成为myData的代理(proxy)
所以你对vm的操作就相当于对myData进行的操作,同时你还能通过this来访问到vm,因为this就是vm,vm就是myData的代理所以再vue中你可以通过this来读取data的值。
然后再通过上面说过的监听来对myData的所有属性进行监控
为什么要监控?
为了防止myData的属性变了,vm还不知道,那知道了又如何?知道了属性变了就能调用render(data)了呀让ui自动刷新了啊,这就是响应啊
Vue 的 Data 的 bug
1、如果有多个key,需要提前声明
2、Vue.set(){} / this.$set(){}可以添加data数据
数据响应式:
响应式即对外界的变化做出的反应的一种形式。
const vm = new Vue({data:{n: 0}}),当修改 vm.n 或 data.n 时,render(data...) 中的 n 就会做出响应的响应。这个联动的过程就是 vue 的 数据响应式。
vue 目前通过 Object.defineProperty 来实现数据响应式