Proxy 是 JavaScript 中的一个内置对象。它可以为一个对象创建“代理”,通过这个代理,可以拦截对这个对象的各种操作。
使用方法
Proxy 的使用非常简单。所有的 Proxy 对象通过 new Proxy(target, handler)
来创建。其中:
- target: 目标对象
- handler: 一个对象,定义要拦截目标对象的哪些操作和对应的处理函数
举个例子:
const target = {
p1: "hello"
};
const handler = {
get(target, prop, receiver) {
console.log(target) // target { p1: "hello" }
console.log(prop) // p1
console.log(receiver === proxy) // true
return 'world'
}
};
const proxy = new Proxy(target, handler);
proxy.p1 // "world"
上面这个例子,就是拦截了 get()
操作,使得通过 proxy.p1 去访问 target 上的 p1 属性时,在 handler 中处理成返回 “world”,所以最终proxy.p1 返回的值是 “world”。
这就是 Proxy 的使用,没有很难理解的语法。接下来我们就来梳理一下,Proxy 具体可以拦截对象上哪些操作。
Handler functions
要看 Proxy 可以拦截哪些操作,就是把 handler 上的方法一一列出来。我们下面通过例子来一一学习。
-
handler.get()
拦截
get()
、Reflect.get()
方法。这个很容易理解,在上面也已经举过例子。这里就不多解释了。 -
handler.set()
拦截
set()
、Reflect.set()
。const obj = { prop: 1 }; const handler = { set(target, prop, value, receiver) { return target[prop] = value * 2; } }; const p = new Proxy(obj, handler); p.prop = 2 p.prop // 4
-
handler.getPrototypeOf()
拦截
Object.getPrototypeOf()
、Object.getPrototypeOf()
、Reflect.getPrototypeOf()
、__proto__
、Object.prototype.isPrototypeOf()
、instanceof
。const obj = { prop: 1 }; const objPrototype = { prop: 2 } const handler = { getPrototypeOf(target) { // target 目标对象,即 obj return objPrototype; } }; const p = new Proxy(obj, handler); console.log(Object.getPrototypeOf(p) === objPrototype); // true console.log(Object.getPrototypeOf(p).prop); // 2
-
handler.setPrototypeOf()
拦截
Object.setPrototypeOf()
、Reflect.setPrototypeOf()
。const obj = { prop: 1 }; const objPrototype = { prop1: 2 } const handler = { setPrototypeOf(target, prototype) { // target 目标对象,即 obj Object.setPrototypeOf(target, { prop1: 3 }) return true; } }; const p = new Proxy(obj, handler); Object.setPrototypeOf(p, objPrototype) console.log(Object.getPrototypeOf(p) === objPrototype); // false console.log(p.prop1); // 3
-
handler.has()
拦截
in
、Reflect.has()
。const obj = { prop1: 1, prop2: 2 } const handler = { has(target, prop) { // target 为目标对象,即 obj // prop 为属性名,即 prop if (prop === 'prop1') { return false; } return prop in target; } }; const p = new Proxy(obj, handler); console.log('prop1' in p); // false console.log('prop2' in p); // true
-
handler.deleteProperty()
拦截
delete
、Reflect.deleteProperty()
。const obj = { canDelete: 'hello', notDelete: 'world' }; const handler = { deleteProperty(target, prop) { // target 为目标对象,即 obj // prop 为属性名 if (prop === "canDelete") { delete target[prop]; } } }; const p = new Proxy(obj, handler) delete p.canDelete delete p.notDelete console.log(p.canDelete) // undefined console.log(p.notDelete) // 'world'
-
handler.apply()
拦截函数的直接调用、
apply()
、call()
,所以目标对象要是一个函数。let obj = { a: 1, b: 2 } function sum() { return this.a + this.b } const handler = { apply: function(target, thisArg, argumentsList) { // target 为目标对象,即 sum // thisArg 为 this,即 obj // argumentsList 为参数组成的数组,即 [1, 2] return target.call(thisArg, argumentsList[0], argumentsList[1]) * 10; } }; const p = new Proxy(sum, handler); console.log(p.call(obj, 1, 2)); // 30
-
handler.construct()
拦截
new
、Reflect.construct()
。function Foo(value) { this.value = value; } const handler = { construct(target, args) { // target 为目标对象,即 Foo // args 为参数组成的数组,即 ["hello"] return new target("world"); } }; const p = new Proxy(Foo, handler); console.log(new p('hello').value); // 'world'
-
handler.ownKeys()
拦截
Object.getOwnPropertyNames()
、Object.getOwnPropertySymbols()
、Object.keys()
、Reflect.ownKeys()
。const obj = { prop1: 1, [Symbol('prop2')]: 2, prop3: 3 }; const handler = { ownKeys(target) { return Reflect.ownKeys(target); } }; const p = new Proxy(obj, handler); for (let key of Object.keys(p)) { console.log(key); // "prop1" // "prop2" }
-
handler.defineProperty()
拦截
Object.defineProperty()
、Reflect.defineProperty()
。const handler = { defineProperty(target, prop, descriptor) { // target 为目标对象,即 obj // prop 为属性名,即 “prop” // descriptor 为属性特性对象,即 { value: 42, writable: false } Object.defineProperty(target, key, { value: descriptor.value, writable: true }) return true; } }; const obj = { prop: 1 }; const p = new Proxy(obj, handler); Object.defineProperty(p, "prop", { value: 42, writable: false }) p.prop = 123 p.prop // 123
-
handler.getOwnPropertyDescriptor()
拦截
Object.getOwnPropertyDescriptor()
、Reflect.getOwnPropertyDescriptor()
。const obj = { prop: 1 }; const handler = { getOwnPropertyDescriptor(target, prop) { // target 为目标对象,即 obj // prop 为属性名,即 prop return { configurable: true, value: 5 }; } }; const p = new Proxy(obj, handler); console.log(Object.getOwnPropertyDescriptor(p, 'prop').value) // 5
-
handler.isExtensible()
拦截
Object.isExtensible()
、Reflect.isExtensible()
。isExtensible()
这个方法是判断对象是否可扩展,不可扩展的对象不能增加新的属性。const obj = {} const handler = { isExtensible(target) { Object.preventExtensions(target); return Object.isExtensible(target); } } const p = new Proxy(obj, handler) console.log(Object.isExtensible(obj)) // true console.log(Object.isExtensible(p)) // false
-
handler.preventExtensions()
拦截
Object.preventExtensions()
、Reflect.preventExtensions()
。preventExtensions()
可以使对象变成不可扩展。const obj = {} const handler = { preventExtensions(target) { Object.preventExtensions(target) return true } } const p = new Proxy(obj, handler) Object.preventExtensions(p) console.log(Object.isExtensible(p)) // false
上面这些就是 Proxy 可以拦截的操作,平常写业务代码,我们可能比较少接触到,但是 Proxy 在一些库里已经被各种使用,比如 vue3 就用 proxy 重写了双向数据绑定。理解 Proxy 的使用,对阅读这些库的源码也是很有帮助的。