代理和反射
代理 Proxy
可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
代理是目标对象的抽象
创建代理
代理是使用
Proxy
构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象
js
// 1. 目标对象
const target = {
id: 'target'
};
// 2. 处理程序对象
const handler = {};
// 3. 创建代理对象
const proxy = new Proxy(target, handler);
// id 属性会访问同一个值
console.log(target); // target
console.log(proxy); // target
// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// hasOwnProperty() 方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false
捕获器 trap
捕获器就是在处理程序对象 (handler) 中定义的“基本操作的拦截器”
js
// 目标对象
const target = {
foo: 'bar'
};
// 处理程序对象
const handler = {
// 定义一个 get() 捕获器,捕获器在处理程序对象中以方法名为键
get() {
return 'handler override';
}
};
// 创建代理对象
const proxy = new Proxy(target, handler);
console.log(target.foo); // bar
console.log(proxy.foo); // handler override
// get() 捕获器会接收到目标对象、要查询的属性和代理对象三个参数
get(trapTarget, property, receiver) {};
有了三个参数,重建被捕获方法的原始行为
js
// 重建被捕获方法的原始行为
const target = {
foo: 'bar'
};
const handler = {
get(trapTarget, property, receiver) {
return trapTarget[property];
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
反射 Reflect
处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。
反射可以更方便程序员重建被捕获方法的原始行为 (修改捕获方法)
js
// 目标对象
const target = {
foo: 'bar'
};
// 处理程序对象
const handler = {
get() {
console.log(...arguments); // { foo: 'bar' } foo { foo: 'bar' }
// 目标对象,查询的属性,代理对象
return Reflect.get(...arguments);
}
};
// 创建代理对象
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
// 简写
const proxy = new Proxy(target, Reflect);
js
// 使用反射重建被捕获方法的原始行为
const target = {
foo: 'bar',
baz: 'qux'
};
const handler = {
get(trapTarget, property, receiver) {
let decoration = '';
if (property === 'foo') {
decoration = '!!!';
}
return Reflect.get(...arguments) + decoration;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar!!!
console.log(target.foo); // bar
console.log(proxy.baz); // qux
console.log(target.baz); // qux
代理捕获器与反射方法
代理可以捕获 13 种不同的基本操作
get(target, property, receiver)
set(target, property, value, receiver)
has(target, property)
defineProperty(target, property, descriptor)
getOwnPropertyDescriptor()
deleteProperty()
ownKeys()
getPrototypeOf()
setPrototypeOf()
isExtensible()
preventExtensions()
apply()
construct()
代理模式
1️⃣ 跟踪属性访问
通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询
js
const user = {
name: 'Jake'
};
const proxy = new Proxy(user, {
get(target, property, receiver) {
console.log(`Getting ${property}`);
return Reflect.get(...arguments);
},
set(target, property, value, receiver) {
console.log(`Setting ${property}=${value}`);
return Reflect.set(...arguments);
}
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27
2️⃣ 隐藏属性
代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举
js
const hiddenProperties = ['foo', 'bar'];
const targetObject = {
foo: 1,
bar: 2,
baz: 3
};
const proxy = new Proxy(targetObject, {
get(target, property) {
if (hiddenProperties.includes(property)) {
return undefined;
} else {
return Reflect.get(...arguments);
}
},
has(target, property) {
if (hiddenProperties.includes(property)) {
return false;
} else {
return Reflect.has(...arguments);
}
}
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true
3️⃣ 属性验证
4️⃣ 函数与构造函数参数验证
5️⃣ 数据绑定与可观察对象
通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作
js
// 把 userList 和 User关联起来,每次创建 User 实例的时候,都会把这个实例添加到 userList 数组中
const userList = [];
class User {
constructor(name) {
this.name_ = name;
}
}
const proxy = new Proxy(User, {
construct() {
const newUser = Reflect.construct(...arguments);
userList.push(newUser);
return newUser;
}
});
new proxy('John');
new proxy('Jacob');
new proxy('Mike');
console.log(userList);
// [User { name_: 'John' }, User { name_: 'Jacob' }, User { name_: 'Mike' }]
把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息
js
const userList = [];
function emit(newValue) {
console.log(newValue);
}
const proxy = new Proxy(userList, {
set(target, property, value, receiver) {
const result = Reflect.set(...arguments);
// Vue 3 就是在这里执行解析模板的操作吧
if (result) {
emit(Reflect.get(target, property, receiver));
}
return result;
}
});
proxy.push('John'); // 会有两行输出,解释如下:
// John 第一次 set 数组对象的 0 属性,value 为 "John"
// 1 第二次 set 数组对象的 length 属性,value 为 "1"
proxy.push('Jacob');
// Jacob 第一次 set 数组对象的 1 属性,value 为 "Jacob"
// 2 第二次 set 数组对象的 length 属性,value 为 "2"