什么数据是响应式
概念:响应即回应,假如一个物体由于外界的刺激而做出反应,那它就是响应式的。
那什么是数据响应式呢?
const vm = new Vue({data:{n:0}})
- Vue的数据存储在data里,当修改vm.n时,会触发视图的渲染。这就是响应。
通过例子来分析
//引用完整版 Vue
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false; //禁用警告
const myData = {
n: 0
}
console.log(myData) // 精髓
let vm = new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(()=>{
myData.n += 10
console.log(myData) // 精髓
},3000)
- 首先声明myDate对象,将n值设置为0,并打印出myDate
- 将myDtae赋值给date,并且设置一个定时器,3s之后n值变为10,并再次打印出myDate
- 打开控制台,观察myDate打印结果

- 通过观察发现两次打印出的结果不同,说明vue对data里的数据进行了一些改造。
为了能够更好的理解其中的奥秘,我们需要理解以下知识点:
例:
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false; //禁用警告
let obj = {
姓: "小",
名: "红",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.slice(1)//或者this.名 = xxx.substring(1)
},
age: 18
};
console.log(`姓 ${obj.姓},名 ${obj.名}`)
console.log(obj)
// obj打印结果
{姓: "小", 名: "红", age: 18}
age: 18
名: "红"
姓: "小"
姓名: (...)
get 姓名: ƒ 姓名()
set 姓名: ƒ 姓名(xxx)
__proto__: Object
说明:Vue通过setter 和 getter对myData的'n'进行改写,这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
监听和代理
需求:无论我给n传的值是正是负,必须输出一个n大于0的值,
let myData = {n:0}
let data = proxy({ data:myData }) // 括号里是匿名对象,无法访问
function proxy({data}){
//监听data的变化
let value = data.n // 声明一个新的 value 来获取 data.n ,监听 data.n 的变化
Object.defineProperty(data, 'n', {
get(){
return value
},
set(newValue){
if(newValue<0)return
value = newValue
}
})
//添加代理
const obj = {}// 声明一个新的对象,把 data.n 全权负责给 obj,这样无轮怎样篡改,
// 都不会影响 n 的变化
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // obj 就是代理
}
console.log(`${data.n}`) // 结果:0
myData.n = -1
console.log(`${data.n},设置为 -1 失败了`) // 结果:0,设置-1失败
myData.n = 1
console.log(`${data.n},设置为 1 成功了`)// 结果:1,设置1成功
- 声明一个myData对象,将n的值存进去
- 声明一个data,让它成为myDta的代理,方便进行监听
- 声明value来存储data.n的值
- 使用Object.defineProperty,产生一个虚拟的属性n,如果读取n的值就调用get函数,如果设置n的值就,调用set函数,进行判断
- 声明一个新的对象obj,让其成为代理,把 data.n 全权负责给 obj,这样无轮怎样篡改,都不会影响 n 的变化
- 以上就是监听和代理的过程
Vue的bug解决
通过案例,我们知道要想实现监听和代理,需要使用Object.defineProperty(obj, 'n', {...}),但如果我忘记传'n',那就会有bug
例1:
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false; //禁用警告
//引用完整版 Vue
Vue.config.productionTip = false;
new Vue({
data: {},
template: `
<div>{{n}}</div>
`
}).$mount("#app");
浏览器报错

import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false; //禁用警告
//引用完整版 Vue
Vue.config.productionTip = false;
//引用完整版 Vue
Vue.config.productionTip = false;
new Vue({
data: {
obj: {
a: 0 // obj.a 会被 Vue 监听 & 代理
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1; //请问,页面中会显示 1 吗?
}
}
}).$mount("#app");
当我点击set b按钮时,页面不会出现1。因为b根本不存在,所以你无法对其进行监听
解决方案一:提前申明好key
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false; //禁用警告
//引用完整版 Vue
Vue.config.productionTip = false;
//引用完整版 Vue
Vue.config.productionTip = false;
new Vue({
data: {
obj: {
a: 0
b:'' //提前声明
//或b:'undefined'
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1; //请问,页面中会显示 1 吗?
}
}
}).$mount("#app");
解决方案二:使用Vue.set或this.$set
//引用完整版 Vue
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {
obj: {
a: 0
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
Vue.set(this.obj,'b',1)
// this.$set(this.obj,'b',1) //这句和上面等同
}
}
}).$mount("#app");
总结:由于Object.definePropery 的限制,Vue无法检测到对象属性的添加或删除。所以Vue不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值,或者使用Vue.set 或 vm.$set 进行设置。
数组的解决方案
使用Vue提供的变异方法操作
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {
array: ["a", "b", "c"]
},
template: `
<div>
{{array}}
<button @click="setD">set d</button>
</div>
`,
methods: {
setD() {
this.array.push('d');
}
}
}).$mount("#app");
尤雨溪对着7个API进行了改造,方便对数组进行增删,这7个API会自动处理监听和代理,并更新视图。具体内容可以参考Vue文档
包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()