关于ES6中的Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),等同于在语言层面做出修改,所以属于一种“元编程”(met

如果你平时有关注 Vue 的进展的话,可能已经知道了在 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。 Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作
Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程
Proxy是构造函数,它有两个参数target和handler
handler 实例方法
| 方法 | 说明 |
|---|---|
| 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循 |
| 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) |
js
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
拦截读取写入
接下来我们通过 Proxy 来实现一个数据响应式
js
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
//
if (typeof target[property] === 'object' && target[property] !== null) {
return new Proxy(target[property], handler);
} else {
return Reflect.get(target, property);
}
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
在上述代码中,我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了
Reflect
Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect不是一个函数对象,因此它是不可构造的。Reflect的所有的方法都是静态的就和Math一样,目前它还没有静态属性。
Reflect对象的方法与Proxy对象的方法相同,更多可以参考Javascript中的Reflect对象
拦截 new 命令 construct()
construct方法用于拦截new命令,下面是拦截对象的写法
js
construct (target, args, newTarget)
- 第一个参数是 目标对象
- 第二个参数是 构造函数的参数对象
- 第三个参数是 创造实例对象时,new命令作用的构造函数
js
var p = new Proxy(function () {}, {
construct: function(target, args, newTarget) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 , newTarget};
}
});
let v = (new p(1)).value // "called: 1" 10
console.log(new p().newTarget === p) // true
Proxy.revocable()
js
Proxy.revocable(target, handler);
revocable 返回值是一个对象,他有两个属性,一个是proxy,就是Proxy的实例,另外一个是revoke,类型是函数,用于移除这个代理
js
let target = {};
let handler = {
get(target, key){
console.log(key);
return target[key];
}
};
let obj = Proxy.revocable(target, handler);
console.log(obj); //{proxy: Proxy, revoke: function}
obj.proxy.foo = 123;
console.log(obj.proxy.foo); // 123
obj.revoke();
console.log(obj.proxy.foo); // TypeError: Revoked
Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问
更多有关文章
ES6中的Proxy、Reflect以及Vue3.0中的应用原理
Proxy
ES6 系列之 defineProperty 与 proxy