本次笔记主要是对 vue 数据响应式过程的研究,研究过程比知识本身更重要,这些方法可以不读源码,也能了解真相.重点在于vue到底对data到了些什么,围绕这个疑问展开我们的推理过程
get 和 set 计算属性
代码实例:
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
const myData = {
n: 0,
};
console.log(myData);
const vm = new Vue({
data: myData,
template: `
<div>{{n}}<button @click = "add">+10</button></div>
`,
methods: {
add() {
this.n += 10;
},
},
}).$mount("#xyx");
setTimeout(() => {
myData.n += 10;
console.log(myData);
console.log(vm);
}, 3000);
结果:
打印出来的 myData 第一个初始值是{n:0} ,而第二个是{_ ob_: Observer}。
也就是说一开始是{n:0},传给 new Vue 之后立马变成{n:...},但是为什么表现和{n:0}一致呢,我们首先要学习一下 ES6 的 getter 和 setter
举个例子
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.slice(1);
},
age: 18,
};
obj3.姓名 = "刘诗诗";
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`);
console.log(obj3);
// 总结:setter 就是这样用的。用 = xxx 触发 set 函数
打印出 obj3
所以从这个例子看出 obj3 只有姓,名,get 姓名 和 set 姓名四个属性,并没有姓名这个属性,浏览器在打印这个姓名的时候后面加...,意思就是确实可以对姓名进行读和写,但是并不存在一个姓名的属性。
所以对 n 的解释是并不存在 n 的属性,只是有一个 get n 和 set n 属性来模拟对 n 的操作。
Object.defineProperty
**Object.defineProperty()** 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
const _xxx = 0;
Object.defineProperty(obj3, "xxx", {
get() {
return _xxx;
},
//因为xxx是虚拟的通过get创造出来的所有当你返回即return xxx,会回去在调用get()然后再return xxx最后无限循环
/**
get(){
return xxx
},
**/
set(value) {
_xxx = value;
},
});
结果:再次打印出 obj3
注意:定义的属性 xxx 是不存在!!!
小结:
Object.defineProperty
可以给对象添加属性 value
可以给对象添加 getter/setter
getter/setter 在定义的时候用于对属性的读写进行监控
啥是代理(设计模式)
对 myData 对象的属性读写,全权由另一个对象 vm 负责
那么 vm 就是 mData 的代理
比如 myData.n 不用,偏要 vm.n 来操作 myData.n
vm = new Vue({data:myData})
一.会让 vm 成为 myData 的代理(proxy)
二.会让 myData 的所有属性进行监控
为什么要监控,为防止 myData 的属性变了,vm 不知道
vm 知道了又如何?知道属性变了就可以调用 render(data)呀!
UI = render(data)
代码:
let data0 = {
n: 0,
};
// 需求一:用 Object.defineProperty 定义 n
let data1 = {};
Object.defineProperty(data1, "n", {
value: 0,
});
console.log(`需求一:${data1.n}`);
//output 0
// 总结:这煞笔语法把事情搞复杂了?非也,继续看。
// 需求二:n 不能小于 0
// 即 data2.n = -1 应该无效,但 data2.n = 1 有效
let data2 = {};
data2._n = 0; // _n 用来偷偷存储 n 的值
Object.defineProperty(data2, "n", {
//外部通过data2.n改变里面的值
get() {
return this._n; //读data2属性的地方
},
set(value) {
//写data2属性的地方
if (value < 0) return;
this._n = value;
},
});
console.log(`需求二:${data2.n}`);
data2.n = -1;
console.log(`需求二:${data2.n} 设置为 -1 失败`);
data2.n = 1;
console.log(`需求二:${data2.n} 设置为 1 成功`);
// 抬杠:那如果对方直接使用 data2._n 呢?
// 算你狠
// 需求三:使用代理
let data3 = proxy({ data: { n: 0 } }); // 括号里是匿名对象,无法访问
function proxy({ data } /* 解构赋值,别TM老问 */) {
const obj = {};
// 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化
// 因为我怕你们看不懂
Object.defineProperty(obj, "n", {
get() {
return data.n;
},
set(value) {
if (value < 0) return;
data.n = value;
},
});
return obj; // obj 就是代理
}
// data3 就是 obj
//把data.n赋值给obj的虚拟n属性 obj代理了data3
console.log(`需求三:${data3.n}`);
data3.n = -1;
console.log(`需求三:${data3.n},设置为 -1 失败`);
data3.n = 1;
console.log(`需求三:${data3.n},设置为 1 成功`);
// 杠精你还有话说吗?
// 杠精说有!你看下面代码
// 需求四
let myData = { n: 0 };
let data4 = proxy({ data: myData }); // 括号里是匿名对象,无法访问
// data3 就是 obj
console.log(`杠精:${data4.n}`);
myData.n = -1;
console.log(`杠精:${data4.n},设置为 -1 失败了吗!?`);
// 我现在改 myData,是不是还能改?!你奈我何
// 艹,算你狠
// 需求五:就算用户擅自修改 myData,也要拦截他
let myData5 = { n: 0 };
let data5 = proxy2({ data: myData5 }); // 括号里是匿名对象,无法访问 data5就是myData5的代理
function proxy2({ data } /* 解构赋值,别TM老问 */) {
// 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化
// 因为我怕你们看不懂
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) {
if (value < 0) return; //这句话多余了
data.n = value;
},
});
return obj; // obj 就是代理
}
// data3 就是 obj
console.log(`需求五:${data5.n}`);
myData5.n = -1;
console.log(`需求五:${data5.n},设置为 -1 失败了`);
myData5.n = 1;
console.log(`需求五:${data5.n},设置为 1 成功了`);
// 这代码看着眼熟吗?
// let data5 = proxy2({ data:myData5 })
// let vm = new Vue({data: myData})
// 现在我们可以说说 new Vue 做了什么了
数据的响应式
Vue 的 data 是响应式
const vm = new Vue({data:{n:0}})
- vue 对 data 做了两件事.
第一件事:vue 对 data 里的对象进行篡改和监听.
第二件事:新生成的对象来代理被窜改的对象 - 这两件事就是通过 object.definepropery 的 getter setter 方法来实现数据响应式的
响应式网页是啥?
如果我改变窗口大小,网页内容会做出响应,那就是响应式网页
小 tips:view 更新 data 的话,通过事件监听即可,比如 input 标签监听 'input' 事件就可以实现了。
data 的 bug
如果属性一开始就不存在,就不会被监听
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");
解决办法
使用 Vue.set 或 this.$set
作用:
新增 key
自动创建代理和监听(如果没创建过)
触发 UI 更新(但并不会立刻更新)
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 吗?
console.log(Vue.set === this.$set);
Vue.set(this.obj, "b", 1);
// this.$set(this.obj, 'b', 1)
},
},
}).$mount("#app");
对象中新增的 key
Vue 没有办法事先监听和代理
要使用 set 来新增 key,来创建监听和代理,更新 UI
最好提前把属性都写出来,不要新增 key
但数组做不到[新增 key]
数组中新增的 key
也可以用 set 来新增 key,且创建监听和代理,更新 UI
VUE 的 7 个 API 方便你对数组的进行增减——变异方法
这 7 个 API 会自动处理监听和代理,并且更新 UI
注意:这里的7个api并不是本身数组上我们认为的原型方法,已经被尤雨溪进行了更改
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()pop()shift()unshift()splice()sort()reverse()
你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })。
英语
reference:引用