Vue的响应式原理是使用数据劫持
https://blog.csdn.net/weixin_44019523/article/details/113574552
Vue2是Object.defineProperty(ES5)
Vue3是Proxy对象(ES6)
Object.defineProperty()
本章将介绍Vue2使用的Object.defineProperty(obj,prop,description)接收三个3个参数
使用介绍
我们把重点集中在第三个参数,description是属性描述符
属性描述符的拥有两种表现形式:数据描述符和存取描述符。这两种描述符不能同时存在。
如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常。 –MDN
- 两种描述符均有以下键值:
(1) configurable :配置。可以修改属性描述符,且属性能被删除,默认false\
let student = {};
Object.defineProperty(student, "sex", {
//默认false
//configurable: false,
});
//没有办法在进行配置,以下都无效
Object.defineProperty(student, "sex", {
value:12,
configurable:true
});
(2) enumerable :枚举。是否读取该属性访问for…in,默认false
let student = {};
Object.defineProperty(student, "job", {
enumerable: false,
})
//for...in 是没办法访问的,student.job却可以
- 数据描述符可选键值
value : 该属性的初始值。可以是任何(数值,对象,函数等)默认undefined。
writable : 当且进档该属性的writable为true时,该属性才能被写入值。默认为false
let student = {};
Object.defineProperty(student, "name", {
//设置值
value: '小明',
//是否可改写
writable:true,
//配置
configurable:true,
//枚举
enumerable: true,
})
- 存取描述符可选键值
get : 一个给属性提供getter的方法。如果没有getter则为undefined。当访问属性时,该方法会被执行。会传入this,但由于继承关系,this并不一定是定义改属性的对象。默认为undefined
set : 一个给属性提供setter的方法。如果没有setter则为undefined。当属性被修改时,该方法会被执行。该方法将接受唯一参数。默认为undefined
注意不能在getset方法内写对象属性,会造成栈溢出
let student = {};
//不可以直接在get方法和set方法内使用student.name输出和写入,因为会被检测到 要使用val代存
let val = student.name;
Object.defineProperty(student, "name", {
get(){
console.log('拦截到获取');
return val
},
set(newVal){
console.log('拦截到写入');
//val自然不等于student.name,但是在get方法上输出的是val呀 没有影响
val = newVal;
}
configurable:true,
enumerable: true,
})
//也可以这么写,使用形参
let student = {};
(function(val){
Object.defineProperty(student, "name", {
get(){
console.log('拦截到获取');
return val
},
set(newVal){
console.log('拦截到写入');
val = newVal;
},
configurable:true,
enumerable: true,
})
})(student.name)
vue2数据劫持
放上清晰明了的代码
/**
* 数据劫持
* 监听this属性的修改和获取,访问this.$data
*/
proxyData() {
for (let key in this.$data) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
console.log('监听到Vue实例对象的写入');
this.$data[key] = newVal;
}
})
}
};
这也暴露了defineProperty导致Vue2必须通过Vue.set方法来进行响应式属性的添加
const obj = {
name: "_island",
age: 18
};
const objProxy = new Proxy(obj, {
// 获取值时的捕获器
get: function (target, key) {
console.log(`监听到了${key}被获取值`);
return target[key];
},
// 设置值时的捕获器
set: function (target, key, newValue) {
console.log(`监听到了${key}被设置值`);
target[key] = newValue;
}
});
console.log(objProxy.name);
// 监听到了name被获取值
// _island
console.log(objProxy.age);
// 监听到了age被获取值
// 18
objProxy.name = "QC2125";
// 监听到了name被设置值
console.log(objProxy.name);
// 监听到了name被获取值
// QC2125
Proxy
Proxy对象用于创建一个对象的代理,是用于监听一个对象的相关操作。代理对象可以监听我们对原对象的操作。Proxy对象需要传入两个参数,分别是需要被Proxy代理的对象和一系列的捕获器。
使用介绍
什么是proxy
const obj={
name:'_island'
}
const objProxy=new Proxy(obj,{});
console.log(objProxy);
打印出来可以看到的是一个Proxy对象。下面我们开始看看Proxy中的捕获器对象。
那么在Proxy也有和defineProperty中 属性描述符 非常相似的参数,就是 捕获器。在实例化Proxy对象时,第二个参数传入的是捕获器集合,我们在其对象内定义一个get捕获器,用于监听获取对象值的操作。
// 定义一个普通的对象obj
const obj = {
name: "_island"
};
// 代理obj这个对象,并传入get捕获器
const objProxy = new Proxy(obj, {
// get捕获器
get: function (target, key) {
console.log(`捕获到对象获取${key}属性的值操作`);
return target[key];
},
});
// 通过代理对象操作obj对象
console.log(objProxy.name);
// 捕获到对象获取name属性的值操作
// _island
在objProxy对象的拦截器中新增一个捕获器set,用于监听对象的某个属性被设置时触发。
// set捕获器
set: function (target, key, val) {
console.log(`捕获到对象设置${key}属性的值操作,新值为${val}`);
target[key] = val;
}
console.log(objProxy.name = "QC2125");
// 捕获到对象设置name属性的值操作,新值为QC2125
console.log(objProxy.name);
// 捕获到对象获取name属性的值操作
// QC2125
我们可以定义一些行为
set: function (target, key, val) {
if (key==='age' && typeof val === "number") {
target[key] = val;
} else {
throw new TypeError("该属性的值必须是Number类型");
}
}
对象中的方法 | 对应触发条件 |
---|---|
handler.getPrototypeOf() | Object.getPrototypeOf 方法的捕捉器 |
handler.setPrototypeOf() | Object.setPrototypeOf 方法的捕捉器 |
handler.isExtensible() | Object.isExtensible 方法的捕捉器 |
handler.preventExtensions() | Object.preventExtensions 方法的捕捉器 |
handler.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor 方法的捕捉器。 |
handler.defineProperty() | Object.defineProperty 方法的捕捉器 |
handler.handler.has() | in 操作符的捕捉器 |
handler.get() | 属性读取操作的捕捉器 |
handler.set() | 属性设置操作的捕捉器 |
handler.deleteProperty() | delete 操作符的捕捉器 |
handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器 |
handler.apply() | 函数被apply调用操作的捕捉器 |
handler.construct() | new 操作符的捕捉器 |
this指向的问题
代理目标对象内部的this会自动改变为Proxy代理对象。
const obj={
name:'_island',
foo:function(){
return this === objProxy
}
}
const objProxy=new Proxy(obj,{})
console.log(obj.foo()); // false
console.log(objProxy.foo()); // true
vue3中的proxy
/**
* 数据劫持
* 监听this属性的修改和获取,访问this.$data
*/
proxyData() {
return new Proxy(this.$data, {
get(target, propKey, receiver) {
console.log('你访问了' + propKey);
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
console.log('你设置了' + propKey);
console.log('新的' + propKey + '=' + value);
Reflect.set(target, propKey, value, receiver);//一个对象上设置一个属性。
}
});
};
总结
关于数据劫持,Object.defineProperty只能监听到具体的对象属性,而proxy是一个的代理对象,它可以代理我们对原目标的操作,也就是能监听到整个对象,这也就是为什么vue3之后不再需要Vue.$set方法了。