什么是响应式
用 Vue 官方文档的一句话介绍响应性:响应性是一种允许我们以声明式的方式去适应变化的编程范例。
如果我们的一个数据改变了,Vue 知道怎么去更新模板以及会更新模板的计算机属性。响应性是 Vue 用来实现UI的核心原理,在用户修改数据的时候,UI会自动更新。
响应式实现过程
- 了解Proxy 原理及有哪些方法, 了解 Reflect 对象
- 了解 WeakMap、Map、Set函数
- 实现响应式
先来看一下 Proxy
Proxy是用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
注意: 不是数据双向绑定,(数据双向绑定是model层和view层之间的关系)
个人理解proxy 应该是一个包装类,就是包装对象的一个方法,其实本质还是这个对象,包装完增加了一些功能而已
写法:
// target: 被 Proxy 代理的对象
// handler: 处理器的对象,设置拦截等基本操作
const p = new Proxy(target, handler)
介绍各个方法
get 方法: 用于拦截对象的读取操作 第三个参数receiver是最初被调用的对象
var original = {
a: 1,
b: 2
};
var p = new Proxy(original, {
get: function (target, property, receiver) {
delete target.b;
console.log(target, property, receiver);
return target.a
}
});
console.log(p.a);
console.log(p);
console.log(original);
set 方法: 用于拦截设置属性值的操作
const monster1 = {eyeCount: 4, abc: 90};
const handler1 = {
set(obj, prop, value) {
console.log(obj, prop, value, ' --------- 参数值');
if (prop === 'eyeCount' && value % 2 !== 0) {
console.log('Monsters must have an even number of eyes');
} else {
return Reflect.set(...arguments);
}
}
};
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
console.log(proxy1.eyeCount);
proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);
deleteProperty 方法: 用于拦截对对象属性的 delete 操作。
var p = new Proxy({}, {
deleteProperty: function(target, prop) {
console.log('called: ' + prop);
return true;
}
});
delete p.a; // "called: a"
applay 方法: 用于拦截函数的调用
function sum(a, b) {
return a + b;
}
const handler = {
apply: function (target, thisArg, argumentsList) {
console.log(`${argumentsList}`);
return target(argumentsList[0], argumentsList[1]) * 10;
}
};
const proxy1 = new Proxy(sum, handler);
console.log(sum(1, 2));
console.log(proxy1(1, 2));
construct 方法: 用于拦截new 操作符
function monster1(disposition) {
this.disposition = disposition;
}
// const monster = new monster1(); 提前 new 无法触发 construct
const handler1 = {
construct(target, args) {
return new target(...args);
}
};
const proxy1 = new Proxy(monster1, handler1);
console.log(new proxy1('fierce').disposition);
getPrototypeOf 方法: 当读取代理对象的原型时,该方法就会被调用
const monster1 = {
eyeCount: 4
};
const monsterPrototype = {
eyeCount: 2
};
const handler = {
getPrototypeOf(target) {
return monsterPrototype;
}
};
const proxy1 = new Proxy(monster1, handler);
console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
console.log(Object.getPrototypeOf(proxy1).eyeCount);
has 方法: 针对 in 操作符的代理方法
const handler1 = {
has(target, key) {
console.log(target, key);
if (key[0] === '_') { // 字符串可以通过这种方法获取到第一个字符
return false;
}
return key in target;
}
};
const monster1 = {
_secret: 'easily scared',
eyeCount: 4
};
const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
console.log('_secret' in proxy1);
console.log('_secret' in monster1);
细节:
- target不能是基础数据类型
- handler可以是空对象 {}
Reflect 对象
- Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。 和 Proxy的操作方法大致相同
- Reflect不是一个函数对象,因此它是不可构造的
- Reflect的所有属性和方法都是静态的(就像Math对象)。
Object.defineProperty 与 Reflect.defineProperty 对比
看一个例子:
var obj = {a: 1, b: 2}
Object.defineProperty(obj, 'c',{
get(){
return 4
}
})
// output {a: 1, b: 2}
obj.c
// output 4
Object.defineProperty(obj, 'c',{
get(){
return 3
}
})
//output VM1139:1 Uncaught TypeError: Cannot redefine property: c
// at Function.defineProperty (<anonymous>)
// at <anonymous>:1:8
Reflect.defineProperty(obj, 'c', {
get(){
return 3
}
})
// output false
结论:
Reflect 可以通过返回值就会知道成功还是失败。 不会导致代码单线程卡住,阻塞下面代码运行
vue3底层的对象响应式的雏形是这样的,利用proxy代理,利用reflect反射(对源数据进行操作)