数据data作为参数,传入到Vue实例后,有一些特殊的变化
import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
const myData = {
n:0,
}
console.log("myData_1",myData);
const vm = new Vue({
data: myData,
template:`
<div>{{n}}<button @click="add">+10</button></div>
`,
methods:{
add(){
this.n += 10;
}
}
}).$mount('#app');
setTimeout(() => {
myData.n += 10;
console.log("myData_2",myData);
},3000);
以上代码运行结果
myData_1 {n: 0}
myData_2 {__ob__: Observer}
为什么会有这样的变化?
用get和set声明对象中的方法
let obj = {
姓:"张",
名:"无忌",
get 姓名(){
return this.姓 + this.名;
},
set 姓名(name){
this.姓 = name[1];
this.名 = name.substring(1);
},
age: 18,
}
console.log("输出结果:"+obj.姓名); // 输出结果:张无忌
obj.姓名 = "令狐冲";
console.log("输出结果:"+obj.姓名); // 输出结果:狐狐冲
对象中使用了get声明的函数,使用起来的时候,不需要加括号,就好像使用一个属性,可以理解为虚拟属性;
对象中使用了set声明的函数,使用起来的时候,直接用“=”赋值,就好比给对象的属性赋值;
Object.defineProperty,给对象添加虚拟属性
let obj = {
姓:"张",
名:"无忌",
get 姓名(){
return this.姓 + this.名;
},
set 姓名(name){
this.姓 = name[0];
this.名 = name.substring(1);
},
age: 18,
}
Object.defineProperty(obj,"infos",{
get(){
return this.姓名 + this.age;
},
set(value){
this.姓 = value[0];
this.名 = value.substring(1,3);
this.age = value.substring(3)
}
})
console.log(obj.infos); // 张无忌18
obj.infos = "韦小宝22";
console.log(obj.infos); // 韦小宝22
数据的代理
// 核心数据{n:0}通过代理暴露出来
let myVueData = proxy({data: {n: 0}});
function proxy({data}) {
const obj = {};
// 实际上应该通过遍历的方法得到data的所有key,包括n,这里是间写
Object.defineProperty(obj,'n',{
get(){
return data.n;
},
set(value){
if(value<0) return;
data.n = value;
}
})
return obj; // 代理对象被暴露出来
}
console.log("myVueData.n = " + myVueData.n); // myVueData.n = 0
myVueData.n = -1; // 代理对象设置值时,进行了拦截,没有设置到n上
console.log("myVueData.n = " + myVueData.n); // myVueData.n = 0
myVueData.n = 100;
console.log("myVueData.n = " + myVueData.n); // myVueData.n = 100
核心数据{n: 0}通过代理暴露出来,用对象myVueData读取n的值,可以保证数据安全;
代理的同时,监听原数据
let priData = {n:0}; // 原数据
let proPriData = proxy2({data: priData}); // 代理数据
function proxy2({data}){
// 删除原数据data中的属性,重新添加
let value = data.n; // 先把核心数据取出来,实际上应该通过遍历的方法得到data的所有key,包括n,这里是简写
// Object.defineProperty(data,'n',{})会覆盖原来的属性n,新的属性n取的值和设置的值都与原来的n关联,但是更安全,新的n修改,也更容易被控制
Object.defineProperty(data,'n',{
get(){
return value;
},
set(newValue){
if(newValue < 0) return;
value = newValue;
console.log("数据已修改了,n = " + this.n);
}
})
// 以上几句话,让data数据改变,能够被代理对象获知并监听,在程序其他位置修改data的值,代理对象也可以知道(比如外部调用priData.n = 100,实际会调用set方法)
const obj = {};
Object.defineProperty(obj,'n',{
get(){
return data.n;
},
set(value){
if(value<0) return;
data.n = value;
}
})
return obj;
}
proPriData.n = 100; // 控制台打印出: 数据已修改了,n = 100
priData.n = 1000; // 控制台打印出: 数据已修改了,n = 100
其实,vue正式这种模式处理数据,比较:
proPriData = proxy2({data: priData}); // 代理数据
vm = new Vue({data:{n:0}}); // vue实例
当我们写vm = new Vue({data:myData})时:
- 会让vm成为数据myData的代理
- 会对myData所哟属性值变化进行监听,这样,vm就能得到数据myData的变化,从而调动render()刷新UI,这说明Vue是数据响应式的,能够响应data的变化;
开头例子变化的原因:
myData_1 {n: 0} // 此时,n是对象的一个属性
myData_2 {__ob__: Observer} // 对象传入Vue实例后,vm成为了数据的代理对象,数据内部的属性n被删除后,重新加了虚拟属性n,数你属性n是一个观察/监听对象
Vue 的 data 的 bug :视图使用了data没有的属性
import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
const myData = {
n: 0,
}
console.log("myData_1", myData);
new Vue({
data: myData,
template: `
<div>{{ n }}
<hr>
m: {{m}}
<hr>
<button @click="add">+10</button>
</div>
`,
methods: {
add() {
this.n += 10;
}
}
}).$mount('#app');
上面的代码中,不会显示m的值,因为传入给Vue的对象中,没有属性m,Vue也就没法进行代理,设置虚拟属性,页面会报错误警告
vue.js?7193:5076 [Vue warn]: Property or method "m" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: v2.vuejs.org/v2/guide/re…. (found in )
Vue 的 data 的 bug 的规避
- 规避方法1,在data中把要用的属性声明好,不使用没有声明的属性;
- 规避方法2,通过Vue.set 和 this.$set 设置没有定义的属性;
const myData = {
n: 0,
obj: {},
}
console.log("myData_1", myData);
new Vue({
data: myData,
template: `
<div>{{ n }}
<hr>
obj.x : {{obj.x}} <button @click="setObjx">set obj.x</button>
<hr>
<button @click="add">+10</button>
</div>
`,
methods: {
add() {
this.n += 10;
},
setObjx(){
console.log("setObjx");
// this.obj.x = "xyz"; // 这是不行的,因为代理对象obj上,没有属性或者虚拟属性x
this.$set(this.obj, 'x', (this.obj.x | 0) +1);// 这是可以的,增加数据属性,或者给没有定义的属性赋值,Vue会自动创建新的属性
// Vue.set(this.obj, 'x', 'xyz123');
}
}
}).$mount('#app');
- Vue.set 和 this.$set 的作用:
- 给对象新增key
- 自动创建代理和监听
- 触发UI更新(异步更新)
数组的变更方法
import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
new Vue({
data: {
array: ["a","b","c"],
},
template: `
<div>
{{array}}
<hr>
<button @click="setD">setD</button>
</div>
`,
methods: {
setD(){
// this.array[3] = "d"; // 不会显示 d ,因为数组 array: ["a","b","c"] 实质上是类似 array: {0:"a",1:"b",2:"c"} 的特殊对象, 没有3这个属性
// this.$set(this.array,3,"d"); // 会显示d,设置对象没有的属性
this.array.push("d"); // 会显示d,因为数组内部结果已经被修改了,新数组方法会在数组操作后添加set方法
}
}
}).$mount('#app');
代码this.array[3] = "d"不能给数组添加新的元素;
代码this.$set(this.array,3,"d")可以给数组添加新的元素,就是设置对象没有的元素;
代码this.array.push("d")可以给数组添加新的元素,这是因为数组传给Vue后,Vue会修改数组的原型链,加一层原型,加的原型里包括pop、push、reverse、shift、sort、splice、unshift等7个方法,这7个方法会在数组操作后,执行set方法,给新数组元素加监听,Vue文档 数组变更方法
结论:数组新增 key 最好通过 7 个 API