该文是深入理解构造选项里的data
const myData={
n:0
}
console.log(myData) //{n:0}
new Vue({
data:myData,
template:`
<div>{{n}}</div>
`
}).$mount("#app")
setTimeout(()=>{
myData.n+=10
console.log(myData) //{n:...}
},3000)
两次console.log(myData)的结果不一样,保留疑问,先学习getter、setter和Object.defineProperty
一、getter、setter
1、getter
let obj1={
姓:"高",
名:"圆圆",
姓名(){
return this.姓+this.名
}
}
console.log(obj1.姓名()) //高圆圆
let obj2={
姓:"高",
名:"圆圆",
get 姓名(){
return this.姓+this.名
}
}
console.log(obj2.姓名) //高圆圆
- 两段代码差异之处为,有无get和打印姓名是有无()
- 这个用法就叫getter,可以获取一个值(不加()的函数,obj2.姓名也叫计算属性)
2、setter
let obj3={
姓:"高",
名:"圆圆",
get 姓名(){
return this.姓+this.名
},
set 姓名(xxx){
this.姓:xxx[0]
this.名:xxx.substring(1)
}
}
obj3.姓名="高媛媛"
console.log(obj3.姓名) //高媛媛
- 这个用法就叫setter,当obj3.姓名时触发set函数
console.log(obj3) //得到如下内容
//姓:高
//名:媛媛
//姓名:(...)
//get 姓名:
//set 姓名:
//...
- 其中,姓名:(...)和上述还存疑的n:...一致
- 姓名:(...)不是一个真实属性,是可以对其读和写,通过getter和setter
- 所以,也并不存在n:...这个属性,是有get n和set n模拟对n的读和写
二、Object.defineProperty
- 在定义完一个对象后,如果想在其额外添加新的get和set,或添加属性value时使用
//接上obj3
var _xxx=0
Object.defineProperty(obj3,'xxx',{
get(){
return _xxx
},
set(value){
_xxx=value
}
})
- 定义的'xxx'属性不存在,如果return this.xxx会死循环,所以用_xxx去承载
1、拓展1:通过getter和setter进行条件判断
//需求:n不能小于0,即data2.n=-1无效,data2.n=1有效
let data2={}
data2._n=0
Object.defineProperty(data2,'n',{
get(){
return this._n
},
set(value){
if(value<0) return
this._n=value
}
})
console.log(data2.n) //0
data2.n=-1
console.log(data2.n) //0(-1无效)
data2.n=1
console.log(data2.n) //1(1有效)
- 思考:如果直接篡改data2._n=-1,console.log(data.n) //-1,需求无法实现
2、使用代理
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就是代理(设计模式)
}
console.log(data3.n) //0
data3.n=-1
console.log(data3.n) //0(-1无效)
data3.n=1
console.log(data3.n) //1(1有效)
- 思考:如果用一个中间值
let myData={n:0};let data4=proxy({data:myData}),myData.n=-1,console.log(data4.n)//-1,需求无法实现
3、监听data
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
}
}) //以上就是加了监听
const obj={}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value<0)return
data.n=value
}
})
return obj
}
console.log(data5.n) //0
myData5.n=-1
console.log(data5.n) //0(-1无效)
myData5.n=1
console.log(data5.n) //1(1有效)
三、理解new Vue
综上代码有:let data5=proxy2({data:myData5}),和let vm=new Vue({data:myData})相似,由此去理解new Vue做了哪些事
- 会让vm成为myData的代理(proxy)
- 会对myData的所有属性进行监控
(截图来自饥人谷课件)
- 防止myData的属性变了而vm不知
- 知道后可调用render(data)更新UI
- getter和setter用于对属性的读写进行监控
四、数据响应式
1、Vue的data是响应式
const vm=new Vue({data:{n:0}})- 若修改vm.n,那么UI中的n就会响应
- Vue通过Object.defineProperty来实现
2、Vue的data有bug
Object.defineProperty(obj,'n',{...})- 必须要有'n',才能监听和代理obj.n
- 那如果忘记给n怎么办?
new Vue({
data:{},
template:`
<div>{{n}}</div> //会给出一个警告,然后什么都不显示
`
}).$mount("#app")
new Vue({
data:{
obj:{ //Vue只检查第一层属性,发现有就不警告了
a:0
}
},
template:`
<div>
{{obj.b}} //但是Vue无法监听一开始不存在的obj.b
<button @click="setB">set b</button>
</div>
`,
methods:{
setB(){
this.obj.b=1 //无法执行,无法显示
}
}
}).$mount("#app")
- 如何解决?使用Vue.set/ this.$set 新增key
Vue.set(this.obj,'b',1)this.$set(this.obj,'b',1)
- 会自动创建代理和监听
- 触发UI更新(但不会立即更新)
3、数组的变异方法
new Vue({
data:{
array:["a","b","c"] //没法提前声明所有key,长度没法预测
},
template:`
<div>
{{array}}
<button @click="setD">set d</button>
</div>
`,
methods:{
setD(){
this.array[3]="d"
Vue.set(this.array,3,'d')
}
}
}).$mount("#app")
- 因为数组长度没法预测,我如何得知是下标为3的要更新
- 所以可将第13、14行代码改写为用push,
this.array.push('d') - 注意:该push不是以前的数组push,而是被尤雨溪修改过的push
- 该push,保留以前push的功能;帮你set,更新UI
- 包括以前的pop、push、reverse、shift、sort、splice、unshift都被修改了
- 修改代码逻辑:
- 先继承数组,记住原来的length
- 当push时,将读出新的length,遍历每个key
- 将每个新增的key都告诉Vue,帮你set
- 对于数组中新增的key
- 用set新增key,会更新UI,但不会创建监听和代理
- 这七个API会更新UI,但不会自动处理监听和代理
- 只用
this.array[n]=xxx,不会更新UI,也不会自动处理监听和代理 - 结论:对数组key的操作最好使用这七个API