代理和反射
Proxy
概述
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
var obj = new Proxy(
{},
{
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
}
);
obj.count = 1; // setting count!
// Proxy 重载了点运算符,用自己的定义覆盖了语言的原始定义。
console.log('obj.count', obj.count);
++obj.count;
// getting count!
// setting count!
console.log('obj.count', obj.count);
new Proxy()
表示生成一个Proxy
实例
var proxy = new Proxy(target, handler);
// target 被代理的对象
// handler 被代理对象上的自定义行为
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
处理函数
函数拦截:
var handler = {
get: function (target, name) {
console.log('get');
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function (target, thisBinding, args) {
console.log('apply');
return args[0];
},
construct: function (target, args) {
console.log('construct');
return { value: args[1] };
}
};
var fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
fproxy(1, 2); // 1
new fproxy(1, 2); // {value: 2}
fproxy.prototype === Object.prototype; // true
fproxy.foo === 'Hello, foo'; // true
下面是 Proxy 支持的拦截操作一览,一共 13 种。
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 - deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
Reflect
概述
Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect 对象的设计目的有这样几个。
(1) 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
(2) 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc)则会返回 false。
(3) 让 Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in obj 和 delete obj[name],而 Reflect.has(obj, name)和 Reflect.deleteProperty(obj, name)让它们变成了函数行为。
(4)Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。
静态方法
Reflect 对象一共有 13 个静态方法。
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
Reflect 和 Proxy 实现观察者模式
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现 observable 和 observe 这两个函数。思路是 observable 函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj =>
new Proxy(obj, {
set
});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
下面代码中,数据对象 person 是观察目标,函数 print 是观察者。一旦数据对象发生变化, print 就会自动执行。
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`);
}
observe(print);
person.name = '李四'; // 输出 // 李四, 20
实现代理监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Proxy Reactive Input</title>
</head>
<body>
<div id="app">
<input
type="text"
id="input"
/>
<p id="output"></p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 要监听和响应的属性
const data = {
text: ''
};
// 定义了 set 捕获器,当属性值改变时,set 捕获器会被触发。
const handler = {
set(target, key, value) {
target[key] = value;
updateView();
return true;
}
};
// data 的代理对象,通过 Proxy 创建
const proxyData = new Proxy(data, handler);
const inputElement = document.getElementById('input');
const outputElement = document.getElementById('output');
// 输入框添加 input 事件监听器,每当输入框的值改变时,更新 proxyData.text 的值
inputElement.addEventListener('input', event => {
proxyData.text = event.target.value;
});
// 负责更新视图,将 proxyData.text 的值显示在 outputElement 中
function updateView() {
outputElement.textContent = proxyData.text;
}
// 初始化视图
updateView();
});
</script>
</body>
</html>