Object.defineProperty()
要想搞懂 vue 侦听数据变化的源理, 我们就必须要了解一下 defineProperty();
它的作用是给对象添加一个属性, 并且可以灵活的对属性进行设置 如:是否可以遍历, 是否可以修改 删除等等
当然, 给对象添加属性有很多方式, 为什么就要用这一种呢?
下面用代码来说明一下
let obj1 = {
name:"huang",
sex:"男"
}
obj1.age = 20; //这里给obj1.添加了一个 age 属性
console.log(obj1); //打印出 obj1 发现age属性添加上去了
let obj2 = {
name:"huang",
sex:"男"
} //这里给obj2 添加了一个age属性
Object.defineProperty(obj2,'age',{
enumerable:false, //默认是true 作用是指定添加的这个age 属性能否被遍历, 也就是说 for ... in 语句中会不会有这个属性
configurable:false, //默认为true 作用是指定这个属性可不可以被删除
writable:false, //默认为true 作用是这个属性可以不以被修改
value:20
})
console.log(obj2);
obj1.age=40; //这里对obj1.age 属性做了修改
console.log(obj1);
obj2.age=30; //这里对obj2.age 属性做了修改, 打印后发现是不可以修改的
console.log(obj2);
我们看一下 console.log的结果
同样 因为obj2中的 age 属性为 enumerable:false , 所以当我们使用 for-in来遍历时, 也没有age属性显示
上面的不是重点, 我们重点是想说, defineProperty中的 getter 和 setter 方法
let obj2 = {
name:"huang",
sex:"男",
_age:0 //原对象上有一个 _age 属性
}
Object.defineProperty(obj2,"age",{ //新增一个属性 age 使用 get , set 方法, 使它的值取决于 _age 属性
enumerable:true, //因为有了 get 和 set 方法, 所里这里面不可以写 writeable 和 value 的配置, 否则会报错
get() {
return this._age;
},
set(value){
this._age = value;
}
})
obj2.age = 100;
console.log(obj2);
obj2._age = 200;
console.log(obj2);
//这样子做的结果就是 当 _age 或者 age发生改变时, 这两个属性的返回值都可以发生变化
上面的例子, 我们做一下衍生, 有两个对象, 我希望 其中一个对象的一个属性, 与另一个对象的一个属性捆绑, 也就是说任一个对象的属性发生了变化, 两个对象都会受到影响
先说一下, 错误的做法
let obj2 = {
name:"huang",
sex:"男",
_age:0
}
let obj3 = obj2;
obj3._age = 40;
console.log(obj2);
上面的做法是错误的, 因为这可不是两个对象, 上面的做法, 其实是同一个对象, 这是一个浅 copy
下面看一下正确的
let obj2 = {
name:"huang",
sex:"男"
}
let obj3 = {
num:10,
count:100
}
//我现在想在 obj2中添加一个 age 属性并且这个age属生 和 obj3中的 num 属性进行绑定,
// 不管是修改 obj2.age 还是修改 obj3.num 两个对象都可以得到更改后的结果
Object.defineProperty(obj2,"age",{
get() {
return obj3.num;
},
set(value){
obj3.num = value;
}
})
obj2.age = 1000;
console.log(obj3);
console.log(obj2);
setTimeout(()=>{ //这里我为什么要 延时5秒执行, 完全是因为 console.log 控制台的 age(...) 我点不急 哈哈
obj3.num = 2000;
console.log(obj3);
console.log(obj2);
},5000)
控制台的输出
从上面可以看到, 我们成功的把 obj2中的属性, 另外的一个对象obj3绑定上关系了, 不管是修改 obj2.age 还是修改 obj3.num 都是可以得到修改的结果的
现在来看一下这个东西和我们要说的 Vue 有什么关系 , 我们vue 的代码如下
let vm = new Vue({
el:"#root",
data:{
name:"huang",
age:"30"
}
})
上面的代码是不是很熟, 我们知道 , vm 中有一个 vm._data 这个属性, 它其实就是我们 声明实例时的 data 对象, 并且 _data中的属性发生变化, data中的也同样发生变化
所以, 我们可以猜到 vue中的 data 和 vm._data 就是两个不同的对象, 但是他们通过 Object.defineProperty 的方式做了捆绑
Object.defineProperty 的一个误区
let obj2 = {
name:"huang"
}
Object.defineProperty(obj2,"name",{
get() {
return this.name;
},
set(v) {
this.name = v;
}
})
console.log(obj2);
上面的代码, 意义上都是有错误的, Object.defineProperty 是新增一个属性了, 而上面的用法, 不是新增一个属性, 而是覆盖了一个属性, 并且我们看一下代码, 当 得到name 属性时, 会去调用 get 方法, 而get 方法中又对得到 name 属性, 就又要去调get 方法, 这是一个没有头的无限递归, 所以上面的代码会报错的, 同样, set 方法和get 方法是一样的会出错
下面写一个 Vue 的侦听属性更改的原理代码, 代码很多功能没有考虑, 只是说有 侦听属性是怎么实现的
let data = {
name:"huang"
}
//定义一个类 传入一个对象, 把传入的对象,和 这个类的属性做捆绑
function Observer(obj){
if(!(this instanceof Observer)){ //如果对象不是使用的 new 关键字就报错
return "请使用 new 关键字,不然的话没有 this"
}
let keys = Object.keys(obj);
keys.forEach((key)=>{
//这里就把传入的对象的属性,都添加到了 Observer 类的对象中, 实现了捆绑
Object.defineProperty(this,key,{
get() {
return obj[key];
},
set(v) {
//在 set 方法中, vue 就可以做以下的事情, 我侦听到了一个 属性发生变化了, 我要去把页面重新渲染一下了
//vue 渲染页面的话, 就会有一系列的反应, 如 虚拟dom 的匹配什么, 就会使页面发生改变
obj[key] = v;
}
})
})
}
let observer = new Observer(data); //我们创建一个Observer的实例对象, 把data中的所有属性和 observer 的属性捆绑了
//同时, 我们再创建一个 vm 对象, 让它的 _data的属性, 等于 observer,就可以了
let vm = {};
vm._data = observer;
data = observer; //注意原来的data没有丢,仍然在被observer引用着,只是data这个变量名被observer占用了,原来的data对象无法用除observer以外的手段找回
//有了上面的代码, 就可以随意更改 vm._data 或者 data 中的数据, 都会触发 set 方法, 也就触发了页面的渲染
以上就是一个简单的 Vue 侦听属性变化的例子
对于这段话的理解 注意原来的data没有丢,仍然在被observer引用着,只是data这个变量名被observer占用了,原来的data对象无法用除observer以外的手段找回, 我们可以使用下面的这段代码证实
let a = {
aa:1,
bb:2
}
class A{
constructor(param) {
this.c = param;
}
}
const m = new A(a) //这里把 a 当做参数传入后, 再去修改 a 的值,
a = "111";
console.log(m.c);
看一下输入结果 m.c 到底是什么,就知道了