上一篇写了构造选项options,data作为内部数据,还有很多东西值得探讨。Vue官方文档在data下提供了参考链接深入响应式原理,这篇文章主要记录对响应式原理的深度学习。
3个例子
例1
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
const myData = {
n: 0
}
console.log(myData)
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(()=>{
myData.n += 10
console.log(myData)
},3000)
复制代码
一开始是{n:0}
,传给new Vue
之后立马变成{n:(...)}
例2
getter和setter
getter
和setter
是ES6的新属性
举例
let obj0 = {
姓: "坏",
名: "东西",
age: 18
};
// 需求一,得到姓名
let obj1 = {
姓: "坏",
名: "东西",
姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求一:" + obj1.姓名());
// 姓名后面的括号能删掉吗?不能,因为它是函数
// 怎么去掉括号?
// 需求二,姓名不要括号也能得出值
let obj2 = {
姓: "坏",
名: "东西",
get 姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求二:" + obj2.姓名);
// 总结:getter 就是这样用的。不加括号的函数,仅此而已。
// 需求三:姓名可以被写
let obj3 = {
姓: "坏",
名: "东西",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx) {
this.姓 = xxx[0];
this.名 = xxx.substring(1);
},
age: 18
};
obj3.姓名 = "好东西";
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`);
// 总结:setter 就是这样用的。用 = xxx 触发 set 函数
复制代码
console
打印obj3
可以看出,可以对姓名
读写,但是并没有姓名
这个属性,是get 姓名
和set 姓名
来模拟对姓名
的操作。
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
如果想要改obj3
的姓名
,需要重新定义一个_xxx
,因为xxx
这个属性实际上不存在。
let obj0 = {
姓: "坏",
名: "东西",
age: 18
};
// 需求一,得到姓名
let obj1 = {
姓: "坏",
名: "东西",
姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求一:" + obj1.姓名());
// 姓名后面的括号能删掉吗?不能,因为它是函数
// 怎么去掉括号?
// 需求二,姓名不要括号也能得出值
let obj2 = {
姓: "坏",
名: "东西",
get 姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求二:" + obj2.姓名);
// 总结:getter 就是这样用的。不加括号的函数,仅此而已。
// 需求三:姓名可以被写
let obj3 = {
姓: "坏",
名: "东西",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx) {
this.姓 = xxx[0];
this.名 = xxx.substring(1);
},
age: 18
};
// 改
var _xxx = 0
Object.defineProperty(obj3,'xxx',{
get(){
return _xxx
},
set(value){
_xxx = value
}
})
obj3.姓名 = "好东西";
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`);
console.log(obj3)
// 总结:setter 就是这样用的。用 = xxx 触发 set 函数
复制代码
例3
总结
-
Object.defineProperty
可以给对象添加属性
value
,可以给对象添加getter
/setter
,getter
/setter
用于对属性的读写进行监控。 -
代理
一种设计模式,对
myData
对象的属性读写,全权由另一个对象vm
负责,那么vm
就是myData
的代理,不如用vm.n
来操作myData.n
-
vm = new Vue({data:myData})
会让
vm
称为myData
的代理(proxy)会对
myData
的所有属性进行监控(为了防止vm
不知道myData
属性变了)vm
知道myData
属性变了,可以调用render(data)
,UI = render(data)
示意图
如果data
有多个属性m、n、k,那么就会有get m、get n、get k等
Vue的data——响应式
const vm = new Vue({data:{n:0}})
如果修改vm.n
,那么UI中的n就会回应,Vue2通过Object.defineProperty
来实现数据式响应
响应式网页是什么?
如果改变窗口大小,网页内容就会做出相应,就是响应式网页
Vue.set
和 this.$set
先看一个例子
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {},
template: `
<div>{{n}}</div>
`
}).$mount("#app");
复制代码
Vue有一条报错,意思是n没被定义,但是在render的时候被引用了
这是Object.defineProperty
的问题,Object.defineProperty(obj,'n',{...})
必须要有一个'n'
,才能监听和代理obj.n
,这里data为空。
再看下面这个例子
import Vue from "vue/dist/vue.js";
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;
}
}
}).$mount("#app");
复制代码
点击页面的按钮,并不是显示1,但也不会报错
这是为什么呢?
因为Vue只会检查第一层属性,第一层没问题就不会报错。点击也不会显示1,因为Vue没法监听一开始就不存在的obj.b
使用Vue.set
或this.$set
就可以解决这个问题
setB() {
// this.obj.b = 1;
Vue.set(this.obj,'b',1)
// 或
this.$set(this.obj,'b',1)
}
复制代码
作用:
新增key
自动创建代理和监听(如果没有创建过)
触发UI更新,但并不会立刻更新
变更方法
举个例子
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[3] = "d"; //页面中不会显示 'd'
this.$set(this.array,3,"d"); // 页面显示'd'
}
}
}).$mount("#app");
复制代码
比如上面这个数组,数组长度可以一直增加,下标就是key,这样没法提前声明所有key,Vue也不能检测到新增了下标,使用Vue.set
或this.$set
,并不会自动添加监听和代理。
尤雨溪的做法是篡改数组API,Vue文档-变更方法
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
复制代码
这7个API被Vue篡改,调用后会更新UI
比如上面的例子,改写一下
setD(){
// this.$set(this.array,3,"d");
this.array.push('d');
}
复制代码
打印array,可以看到,原型上有这7个方法
this.$set
作用于数组时,并不会自动添加监听和代理。
使用Vue提供的数组变更API时,会自动处理监听和代理,并更新UI,所以数组新增key最好通过这7个API。