问题引入:Vue对data做了什么
data值变了,例子中 myData 在传入前和传入后,两次log出的结果不一样
1 | const myData = { |
为什么两次log出的myData会不一致?需要先了解一下ES6中的getter和setter
1 | let obj0 = { |
- 虽然在声明obj3时并未声明 姓名 这样的属性,但你确实可以对它进行读、写的操作,打印出的结果为 姓名: (…)
- n: (…) 的意思就是并不存在n这样的属性,但是它身上有一个 get n 和 set n 来模拟对n的读写操作
Object.defineProperty()
- 定义完一个对象后,想要在它身上再添加额外的getter和setter时使用Object.defineProperty()
- 可以给对象添加属性value,也可以添加getter、setter
- getter、setter用于对属性的读写进行监控
1
2
3
4
5
6
7
8
9
10let _xxx = 0 // 声明一个容器,用来存放 xxx 的值
Object.defineProperty(obj1, 'xxx', {
get() {
return _xxx
},
set(value) {
_xxx = value
}
}) // 给obj1增加了一个名为 xxx 的属性,值为0
使用Object.defineProperty()完成几个需求
需求一:用Object.defineProperty()定义n的值
1 | let data0 = { |
需求二:n不能小于0,即data2.n = -1 无效,data2.n = 1有效
1 | let data2 = {} |
使用代理,解决上述问题
1 | let data3 = proxy({ // 括号里是匿名对象,无法访问 |
需求四:解决绕过代理的问题,就算有人修改myData,也要拦截他
下面是可以被用户修改的myData1
2
3
4
5
6let myData = { n:0 } // 声明一个引用,这样就可以绕过代理,直接修改myData
let data4 = proxy({ data: myData }) // 括号里是匿名对象,无法访问
console.log(`杠精:${data4.n}`)
myData.n = -1 // 绕过代理,成功修改了myData
console.log(`杠精:${data4.n}, 设置为-1成功了`)
解决绕过代理的问题,不要让用户擅自修改myData1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36let myData5 = {n:0}
let data5 = proxy2({ data:myData5 })
function proxy2({data}) {
let value = data.n
// 省略了delete.data.n,使用Object.defineProperty时会覆盖掉
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 进行代理
console.log(`需求4:${data4.n}`)
myData5.n = -1
console.log(`需求4:${data4.n} 设置为 -1 失败`)
myData5.n = 2
console.log(`需求4:${data4.n} 设置为 1 成功`)
什么是代理
- 对 myData 对象的属性进行读写,全权由另一个对象vm来负责
- 那么,vm 就是 myData 的代理
不用 myData.n 偏偏用 this.n 或 vm.n
new Vue做了什么
1
2
3// 看着很相似的两行代码
let data4 = proxy({data: myData4})
let vm = new Vue({data: myData})会让vm成为myData的代理,可以通过 this.myData 访问到 myData
- 会监听 myData 的所有属性(赋值给value,再通过getter、setter添加虚拟属性)
- 监听的目的是防止 myData 属性改变,vm不知道
- 当vm监测到myData发生改变,就调用render(data),刷新视图
Vue的数据响应式
Vue 的 data 是响应式的
- const vm = new Vue({data: {n:0}})
- 如果修改 vm.n ,那么UI中的n就会进行响应
- Vue是通过 Object.defineProperty 来实现数据响应式的
Vue 的 data 的bug
问题:如果一开始在data中并未对 b 进行赋值,Vue就不能对它进行代理或监听1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27new Vue({
data: {
obj: {
a: 0 // obj.a 会被Vue监听和代理
}
},
template: `
<div>
{{ obj.b }}
<button @click="setB">set b</button>
<button @click="addB">b+1</button>
</div>
`,
methods: {
setB(){
this.obj.b = 1 // 这种写法页面中不会显示b的值,因为Vue没办法监听一开始就未设置在data中的obj.b
// 解决方法,使用Vue.set 或 this.$set
console.log(Vue.set === this.$set) // 下面两种写法等价
Vue.set(this.obj, 'b', 1)
this.$set(this.obj, 'b', 1)
},
addB() {
this.obj.b += 1
}
}
}).$mount("#app")
解决方法:使用 Vue.set 或 this.$set,作用是:
- 新增key
- 自动创建监听和代理
- 触发ui进行更新(但并不是立即更新)
eg:this.$set(this.object, ‘m’, 10)
如果data是一个数组呢?
1 | new Vue({ |
当你将一个数组传给Vue的data,Vue会对这个数组进行篡改,它会在原来数组对象中间增加一层原型,这个原型上有7个方法
来看看push是如何被篡改的?1
2
3
4
5
6
7
8
9
10class VueArray extends Array {
push(...args) {
const oldLength = this.length // this就是当前数组
super.push(...args)
console.log('你 push 了')
for(let i = oldLength; i<this.length; i++){ // i为所有push的元素的下标,遍历旧数组到新数组的下标
Vue.set(this, i, this[i]) // 在当前数组this上,添加i项,每项i的值为this[i],并将每个新增的key通过Vue.set告知给Vue
}
}
}