在 Vue 项目中,通过 let vm = new Vue({data: myData})
就可以在当数据发生变化后,使用到该数据的视图也会相应进行自动更新,这是为什么呢?
本文不从源码分析 Vue 对 data 做了什么(毕竟我也读不懂源码😜),只为记录学习到的研究方法,有时,方法比知识本身更重要不是么(授人以鱼不如授人以渔)
首先,这里卖个关子,撇开 Vue,学几个 ES6 的新语法
getter / setter
get 语法将对象属性绑定到查询该属性时将被调用的函数
当尝试设置属性时,set 语法将对象属性绑定到要调用的函数
let obj = {
_num: 0,
get num() { return this._num },
set num(n) { this._num = n }
}
console.log(obj.num)
obj.num = 1
console.log(obj.num)
大白话讲它们的用法就是 getter 调用时不加括号的函数,setter 用 = xxx
对属性赋值
Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
它的语法
Object.defineProperty(obj, prop, descriptor)
现在有一个需求,data 对象的 n 属性值不能小于0
结合 getter / setter 的用法
let data1 = {}
data1._n = 0 // _n 用来偷偷存储 n 的值
Object.defineProperty(data1, 'n', {
get(){
return this._n
},
set(value){
if(value < 0) return
this._n = value
}
})
console.log(`需求一:${data1.n}`)
data1.n = -1
console.log(`需求一:${data1.n} 设置为 -1 失败`)
data1.n = 1
console.log(`需求一:${data1.n} 设置为 1 成功`)
倘若这里直接修改 data1._n
呢?
可以发现 data1.n
也被修改了
考虑将 _n
包装为一个匿名对象的属性,通过代理函数访问
代理
let data2 = 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 就是代理
}
console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n},设置为 -1 失败`)
data2.n = 1
console.log(`需求二:${data2.n},设置为 1 成功`)
假如先定义 myData = {n:0}
,再将 myData
对象传递给匿名对象(data2 = proxy({ data:myData })
),则仍可以通过 myData.n
来修改 data2.n
的值,所以需要对代理接收的数据进行监听
数据监听
let myData = {n:0}
let data3 = proxy2({ data:myData })
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){
data.n = value
}
})
return obj // obj 就是代理
}
console.log(`需求三:${data3.n}`)
myData.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败了`)
myData.n = 1
console.log(`需求三:${data3.n},设置为 1 成功了`)
Vue 的 data
现在,回过头来看看 let vm = new Vue({data: myData})
做了什么
让
vm
成为myData
的代理,由vm
全权负责myData
对象的属性读写对
myData
的所有属性进行监听,当myData
的属性发生变化则通知vm
调用render(data)
更新 UI
Vue.set 和 this.$set
看前面 Object.defineProperty()
的用法可以知道必须要有一个 prop
才可以监听代理 obj[prop],在Vue 文档中 [Vue 示例] -> [数据与方法] 一节中也提及
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中,比如:
var vm = new Vue({ data: { a: 1 }})
。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。也就是说如果你添加一个新的 property,比如:vm.b = 'hi'
那么对 b 的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。
但如果一开始不知道这个 property
,则可以通过 Vue.set()
或 vm.$set()
动态添加
作用:
新增 property
自动创建代理和监听
触发 UI 更新(但不会立即更新)