Vue data 数据属性
1.数据属性 Data的了解
通过前面的学习,我们知道vue的数据是写在vue选项对象(也可以理解为配置对象)的data属性中的,我们可以通过Mustache(双大括号语法)就数据插入到页面上
<div id="app">
<p>第二条狗狗的名字是:{{dogs[2]}}</p>
<p>所有狗狗名字是:{{dogs}}</p>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
dogs: ["哈士奇","中华田园犬","藏獒"]
}
})
</script>
示例的感悟:
- 会发现,如果将这个数组或对象输出到页面上,并不会像JS一样输出
[Object object]
- Vue会输出JSON编码后的值,这样在调试的时候超级有用
- 调试时将内容渲染到页面比将数据输出到控制台更加有效果,
- 在页面上显示会随着值的变化而变化(响应式),,这样更加直观的看到数据的变化
2.数据响应变化
当一个 Vue 实例被创建时,通过插值语法将数据显示在页面上,当我们修改数据时,页面也会发生变化
// 我们的数据对象
var data = { a: 1 }
// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
el:"#app",
data: data
})
// 获得这个实例上的属性
// 返回源数据中对应的字段
vm.a == data.a // => true
// 设置属性也会影响到原始数据
vm.a = 2
data.a // => 2
// ……反之亦然
data.a = 3
vm.a // => 3
为什么页面会自动发生变化呢?
- 这就是因为在Vue实例化时,就会向Vue的响应式系统中加入
data
对象中能够找到的所有属性。 - 其实是Vue通过监听的方式一直在侦查值是否发生变化,我们可以在控制台上打印实例:
图如下:
通过这张图,我们就会发现:
- 示例中data数据属性中的数据
a
被Vue
实例通过get
,和set
一直在检测着, - 也就是说vue的响应系统一直在观察数据
a
的变化,一旦发生变化,响应系统做出反应,改变视图.
所以当数据值发生改变,视图也会产生响应。
2.1 认识数据响应式的变化
通过刚才例子了解当数据发生变化时,页面视图也会进行重新渲染,值得注意的时,只有data
中存在的属性才是响应的。
也就是说如果你添加一个新的属性,比如:
vm.b = 'hi'
那么对b的改动不会出发任何视图的更改。因为vue并没有将b
加入到响应系统中, 说白了就是没有检测b
的变化
那如何解决此类问题呢?
如果你知道你会晚些时候需要一个属性,那么你可以在开始时设置一些初始值。如:
data: {
a: "hello",
b: ''
}
这里要注意一个特例:
这个例外是使用 Object.freeze()
,这个方法是冻结方法,意思是不允许修改对象的属性值,
这就会阻止修改现有的属性, 数据没发改变,也就不会触发Vue的响应系统
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
这个示例中我们利用了Object.freeze()
冻结方法,让Vue实例的所有data属性中的数据都失去了响应式, 这样做没什么意义, 做了时候,Vue也就失去了数据驱动的功能
但是Object.freeze()
方法在某些时候还是非常有用的, 当然不是冻结整个data
,我们可以冻结data属性中的一些对象数据,
想了解,接着往下看
2.2 对象数据的响应变化
2.2.1 了解对象数据的响应变化
刚看到的是data属性只是是基本类型的问题, 接下来看看数据属性值是对象的状况
vue在初始化的时候会循环data中的数据,(数据劫持),以此增加getter和setter来监听数据
示例:
<div id="app">
{{student.name}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
student: {
name:"xiaoming"
}
}
})
</script>
页面显示结果
此时页面正常显示结果, 如果我们在控制台改变对象数据,会有如下结果:
通过示例了解到,Vue数据如果是对象student
,student
中的属性name
在发生改变的时候,页面也会发生改变,说明对象中的name
属性是被vue关注着的
那么问题来了, Vue会检测到student
数据的变化, 那么如果student
对象中的新镇一些属性,会不会被检测到呢.
我们可以通过如下的示例进行测试:
<div id="app"><!---view--->
{{student.name}}
{{student.age}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
student: {
name:"xiaoming"
}
}
})
// 会发现初始化存在的name属性会触发页面的更新
//setTimeout(() => {
// vm.student.name = "小明";
//}, 2000)
// 对象age的改变不会触发页面的更新
//setTimeout(() => {
// vm.student.age = 18;
//}, 2000)
// 但是我们会发现,如果页面一旦更新,age的值也会渲染在页面上,
//原因在于Vue get和set方法监听的是对象,不是对象的每一个属性
setTimeout(() => {
vm.student.name = "小明";
vm.student.age = 18;
}, 2000)
</script>
通过上面三个定时器的不同测试结果, 我们了解到了:
- Vue初始化时定义的数据
student
的改变, - 以及
student
数据的name
属性发生变化都会触发响应系统, 进而改变视图, - 但是如果
student
数据新增一个属性, 却没有触发响应式系统, 页面也没有重新渲染,
为什么会这样呢?
我们可以在控制台打印Vue实例对象
Vue会检测
student
就不多讲了
在看另外一张图
通过这张图我们就会发现,Vue不但会监听
student
数据,还会监听在Vue实例化时,student
就已经存在的属性name
,所以name
的改变也会触发响应系统,
但是我们后添加的对象的数据不是响应式, Vue不会根据这些数据的变化来改变对应的视图,所以当你单独新增一个属性是不会触发响应系统,
通过示例,我们有发现不会触发响应系统的数据属性跟具有响应系统的属性一起改变,也会到处页面的重新渲染, 因为有人触发了响应系统,如果你稍后还是单独改变age
数据值,页面依然不会变化
总结:
- 在实例化Vue之前,就已经定义好的对象,已经对象中的数据会被Vue响应式检测
- 数据对象新增的属性因为没有被检测,所以单独改变不会触发Vue响应式
- 但是只要Vue响应式被触发了,新增的属性值也会触发页面的改变
2.2.2 将不是响应系统检测的数据添加到响应系统
那么如何才能在给对象添加新的属性触发vue的响应系统,让页面发生更新呢?
可以通过 vm.$set()方法可以给对象添加响应式的数据变化
<div id="app"><!---view--->
{{student.name}}
{{student.age}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
student: {
name:"xiaoming"
}
}
})
// 通过$set 方法改变对象的属性会触发响应式
// 让页面视图发生更新
setTimeout(() => {
vm.$set(vm.student,"age",18)
}, 2000)
</script>
为什么$set()会触发页面的 改变呢? 我们可以在控制台上打印Vue实例对象
我们会发现通过$set
方法处理后,Vue会将age
属性添加到响应系统上,所以就触发了响应系统, 进而改变视图
2.2.3 修改多个不是响应系统检测的数据
但是$set方法并不好,如果我要设置100个属性那不得累死,怎么办呢?
所以我们可以采用直接替换属性对象内容的方法,因为响应系统会测试student
数据整体的变化
<div id="app"><!---view--->
{{student.name}}
{{student.age}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
student: {
name:"xiaoming"
}
}
})
// 因为vue实时监听对象student的变化
// 所以我们可以采用替换对象值的方法来触发响应式
setTimeout(() => {
vm.student = {
name: "xiaoming",
age: 18
}
}, 2000)
</script>
通过上面的例子
我们会发现$set每次只能新增一个属性,如果我要新增多个属性就不是那么友好了,
2.2.4 解决直接替换对象数据的缺点
同样的如果采用替换原对象,通过字面量的方式替换,会发现如果我原对象已有多个属性,在通过替换原对象的方式触发响应式的时候,需要不断重写原对象的属性, 就很繁琐.
所以关于替换原对象,我们可以采用Object.assign
来给原对象扩展属性,然后在赋值给原对象,比如
//不要这样,这样并没有新的对象,还是在原有对象上新增属性
// 这种写法跟vm.student.age = 18 完全等价 不会触发响应式
Object.assign(vm.student,{
age:18
})
//你应该这样做, 在合并后形成新的对象, 在把新对象赋值给vue的数据对象
// 这样多就会触发响应式
vm.student = Object.assign({},vm.student,{
age:18
})
2.3 数组数据的响应变化
在操作数组是不能使用下标的方式去改变数据
因为这种方式改变数据的值并不会触发页面的重新渲染
因为通过下标,去改变数组中的某一项是监控不到的
<div id="app">
{{arr}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4, 5, 6]
}
})
// 通过下标改变内容,不会触发响应式,页面不会重新渲染
// 但是数组的值会发生改变
setTimeout(() => {
vm.arr[0] = 5;
vm.arr.length = 2;
}, 5000)
</script>
以上的写法虽然改变了数组,但是不会同步渲染到页面中去,因为vm没有get,set 检测数组的每一项;
我们可以在控制台上打印实例对象查看Vue是否在监听数据的每一项
通过Vue实例对象,我们就了解到Vue并没有检测数据的每一项.
那么我们如何让数组变成响应式的呢?
我们会发现Vue的响应系统是监听着数组本身的,所以我们只能采用直接替换原数组的方案
<div id="app"><!---view--->
{{arr}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
arr:[1,2,3,4,5]
}
})
// 采用替换原数组的方案,触发响应式,重新渲染页面
setTimeout(() => {
vm.arr = [5,2]
}, 2000)
</script>
也可以通过数组的改变原数组的方法来触发响应系统
变异方法;
pop push shift unshift sort reserve splice()
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。
非变异 (non-mutating method) 方法,例如:filter()
, concat()
和 slice()
。这些不会改变原始数组,但总是返回一个新数组。
变异方法触发响应系统的示例
<div id="app"><!---view--->
{{arr}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
arr:[1,2,3,4,5]
}
})
// 采用会改变原数组的一些方法来触发响应系统
setTimeout(() => {
// vm.arr.push(6)
// vm.arr.pop()
// vm.arr.unshift(6)
// vm.arr.shift()
// vm.arr.splice(1,2,9,8,7)
// vm.arr.sort(() => {
// return -1
// })
// vm.arr.reverse();
}, 2000)
</script>
其实并不是这些方法本身会触发响应系统,而是Vue 包含观察数组的变异方法的功能,所以它会触发响应系统, 然后更新视图
当使用非变异方法时,可以采用新数组替换旧数组:
<div id="app"><!---view--->
{{arr}}
</div>
<script src="../vue.js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
arr:[1,2,3,4,5]
}
})
// 用修改后返回的数组直接覆盖原数组来触发响应系统
setTimeout(() => {
vm.arr = vm.arr.map(item => item*2)
}, 2000)
</script>
虽然我们对响应式系统的底层原理不是那么的明白,
但至少现在我们就能知道怎样改变数据会触发响应系统,从而更新页面渲染,知道什么样的方法不会触发页面渲染