Appearance
Proxy代理器的使用
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy 相关词汇
- handler:包含拦截器等占位符对象,可以理解为处理器对象;
- tarps:提供属性访问的方法,这类似于操作系统中捕获器的概念;
- target:被 Proxy 代理虚拟化的对象。
Proxy 语法
const p = new Proxy(target, handler)
参数
target
:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至是另外一个代理对象);
target
:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为;
Proxy 小试牛刀
这里创建一个 person
对象,并初始化 2 个属性,分别 name
为 张三
,age
为 18
,接着使用 Proxy 去代理它,返回一个 person
代理对象,最终使用赋值、取值来验证是否代理成功。
js
// 创建 person 对象,并初始化 name、age
let person = {};
person.name = '张三';
person.age = 18;
// 使用 new Proxy 创建 person 的代理对象
person = new Proxy(person, {
get(target, key) {
console.log(`getting ${key}!`);
return target[key];
},
set(target, key, value) {
console.log(`setting ${key}!`);
target[key] = value;
}
});
// 更改 person 的属性
person.name = '李四';
person.age = 20;
// 获取 person 的属性
console.log(person.name);
console.log(person.age);
在 new Proxy()
里面包含了 2 个参数,person
就是需要代理的对象,{...}
对象就是我们所说的 handler
对象。
在 handler
对象中有 2 个方法,分别是 get
、set
,这两个方法就是所谓的 tarps(拦截器)
。
以上代码输出内容
setting name!
setting age!
getting name!
李四
getting age!
20
可以看到当我们使用 person.xxx=xxx
设置属性的时候会进入我们写的 set
拦截器中,同样当我们使用 person.xxx
获取属性的时候会进入 get
拦截器,这样我们就可以在 set
、get
代码中写入我们自己的逻辑,例如使用 console.log
拦截信息,也可以使其获取固定值;
当然 traps(拦截器)
方法不只只有 get
、set
这两个,接下来列出所有的拦截器;
Proxy 拦截器
handler
对象是一个包容一批特定属性的占位符对象。它包含有 Proxy
的各个拦截器(trap)。
所有的拦截器都是可选的,如果没有定义某个拦截器,那么就会保留源对象的默认行为。
handler.apply()
描述
handler.apply()
方法用于拦截函数的调用。
语法
js
var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});
参数
以下是传递给 apply 方法的参数,this
上下文绑定在 handler 对象上。
target
:目标对象(函数)。
thisArg
:被调用时的上下文对象。
argumentsList
:被调用时的参数数组。
返回值
apply
方法可以返回任何值。
作用于
该方法会拦截目标对象的以下操作:
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
代码示例
js
// 创建一个sum函数,返回a+b的值
function sum(a, b) {
return a + b;
}
// 把sum函数包装成一个代理函数,使得sum函数的参数总是为10和20
const sumProxy = new Proxy(sum, {
apply: function (target, thisArg, argumentsList) {
console.log('target', target);
console.log('thisArg', thisArg);
console.log('argumentsList', argumentsList);
// 在这里可以对参数进行修改
argumentsList[0] = 10;
argumentsList[1] = 20;
return target.apply(thisArg, argumentsList);
}
});
// 未添加代理的sum函数
console.log(`未添加代理的结果: ${sum(1, 2)}`);
// 添加代理的sum函数
console.log(`添加代理的结果: ${sumProxy(1, 2)}`);
输出内容
未添加代理的结果: 3
target ƒ sum(a, b) {
return a + b;
}
thisArg undefined
argumentsList (2) [1, 2]
添加代理的结果: 30
handler.construct()
描述
handler.construct()
方法用于拦截 new
操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target
必须是有效的)。
语法
js
var p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
}
});
参数
下面的参数将会传递给 construct
方法,this
绑定在 handler 上。
target
:目标对象(函数)。
argumentsList
:被调用时的参数数组。
newTarget
:最初被调用的构造函数,上面的例子而言是 p。
返回值
construct
方法必须返回一个对象。
作用于
该拦截器可以拦截以下操作:
new Proxy(...args)
Reflect.construct()
代码示例
js
// 创建一个person函数,用于创建对象,需要接收两个参数,name和age
function person(name, age) {
this.name = name;
this.age = age;
}
// 创建一个personProxy代理,用于代理person函数,当使用new关键字调用personProxy时,会触发construct拦截
const personProxy = new Proxy(person, {
construct(target, args) {
console.log(`Creating a ${target.name}`);
console.log(`Creating args ${args}`)
return new target(...args);
}
})
// 使用new关键字调用personProxy函数,会触发construct拦截
const me = new personProxy('John', 30);
输出内容
Creating a person
Creating args John,30
handler.defineProperty()
描述
handler.defineProperty()
用于拦截对象的 Object.defineProperty()
操作。
语法
js
var p = new Proxy(target, {
defineProperty: function(target, property, descriptor) {
}
});
参数
下列参数将会被传递给 defineProperty
方法。this
绑定在 handler 对象上。
target
:目标对象(函数)。
property
:待检索其描述的属性名。
descriptor
:待定义或修改的属性的描述符。
返回值
defineProperty
方法必须以一个 Boolean
返回,表示定义该属性的操作成功与否。
作用于
该拦截器可以拦截以下操作:
Object.defineProperty()
Reflect.defineProperty()
代码示例
js
// 创建一个person对象
let person = {};
// 创建person对象的代理
personProxy = new Proxy(person, {
defineProperty(target, property, attributes) {
// 输出需要修改的属性和属性描述符
console.log('called: ' + property + 'attributes: ' + JSON.stringify(attributes));
// 调用Reflect.defineProperty()方法修改属性
return Reflect.defineProperty(target, property, attributes)
}
});
// 修改person的age属性
Object.defineProperty(personProxy, 'age', {
configurable: true,
enumerable: true,
value: 18,
type: 'number' // ERROR,此行代码不会生效
});
// 输出person的age属性描述符
console.log(Object.getOwnPropertyDescriptor(personProxy, 'age'));
输出内容
called: ageattributes: {"value":18,"enumerable":true,"configurable":true}
{value: 18, writable: false, enumerable: true, configurable: true}
上述代码中有一行代码 type: 'number'
,这行代码并不会生效,因为可以从输出内容可以看出并没有 type
字段,这是因为递给 defineProperty
的 descriptor
有一个限制 - 只有以下属性才有用,非标准的属性将会被无视: enumerable
、 configurable
、 writable
、 value
、get
、set
。
handler.deleteProperty()
描述
handler.deleteProperty()
方法用于拦截对对象属性的 delete
操作。
语法
js
var p = new Proxy(target, {
deleteProperty: function(target, property) {
}
});
参数
deleteProperty
方法将会接受以下参数。this
被绑定在 handler 上。
target
:目标对象(函数)。
property
:待删除的属性名。
返回值
deleteProperty
必须返回一个 Boolean
类型的值,表示了该属性是否被成功删除。
作用于
该拦截器可以拦截以下操作:
- 删除属性:delete proxy[foo] 和 delete proxy.foo
Reflect.deleteProperty()
代码示例
js
// 创建一个person对象,包含name、age属性
let person = {
name: '张三',
age: 18
}
// 创建一个代理对象
let personProxy = new Proxy(person, {
deleteProperty(target, p) {
console.log('delete', p);
Reflect.deleteProperty(target, p);
}
});
// 删除personProxy的name属性
delete personProxy.name;
输出内容
delete name
handler.get()
描述
handler.get()
方法用于拦截对象的读取属性操作。
语法
js
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
参数
以下是传递给 get 方法的参数,this 上下文绑定在
handler 对象上。
target
:目标对象。
property
:被获取的属性名。
recevier
:Proxy 或者继承 Proxy 的对象。
返回值
get 方法可以返回任何值。
作用于
该方法会拦截目标对象的以下操作:
- 访问属性:
proxy[foo] 和
proxy.bar
- 访问原型链上的属性:
Object.create(proxy)[foo]
Reflect.get()
`
代码示例
js
let person = {
name: '张三',
age: 18,
};
let personProxy = new Proxy(person, {
get(target, property, receiver) {
console.log('get', property);
return Reflect.get(target, property, receiver);
}
});
personProxy.name;
personProxy.age;
输出内容
get name
get age
handler.getOwnPropertyDescriptor()
描述
handler.getOwnPropertyDescriptor()
方法是 Object.getOwnPropertyDescriptor()
的钩子。
语法
js
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
参数
下列参数会被传入 getOwnPropertyDescriptor
方法中。这是绑定到 handler 上。
target
:目标对象。
prop
:返回属性名称的描述。
返回值
getOwnPropertyDescriptor
方法必须返回一个 object 或 undefined
。
作用于
这个陷阱可以拦截这些操作:
代码示例
js
let person = {
name: '张三',
age: 18,
};
let personProxy = new Proxy(person, {
getOwnPropertyDescriptor(target, prop) {
console.log(`called: ${prop}`);
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
console.log(Object.getOwnPropertyDescriptor(personProxy, 'name'));
输出内容
called: name
{value: '张三', writable: true, enumerable: true, configurable: true}
handler.getPrototypeOf()
描述
handler.getPrototypeOf()
是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。
语法
js
const p = new Proxy(obj, {
getPrototypeOf(target) {
...
}
});
参数
当 getPrototypeOf
方法被调用时,this
指向的是它所属的处理器对象。
target
:目标对象。
返回值
getPrototypeOf
方法的返回值必须是一个对象或者 null
。
作用于
在 JavaScript 中,下面这五种操作(方法/属性/运算符)可以触发 JS 引擎读取一个对象的原型,也就是可以触发 getPrototypeOf()
代理方法的运行:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
instanceof
代码示例
js
let person = {};
personProxy = new Proxy(person, {
getPrototypeOf(target) {
console.log('getPrototypeOf');
return Reflect.getPrototypeOf(target);
}
});
// 以下是 5 种 触发 getPrototypeOf 代理方法的方式
console.log(Object.getPrototypeOf(personProxy) === person.__proto__);
console.log(Reflect.getPrototypeOf(personProxy) === person.__proto__);
console.log(personProxy.__proto__ === person.__proto__);
console.log(Object.prototype.isPrototypeOf(personProxy));
console.log(personProxy instanceof Object);
输出内容
getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true
handler.has()
描述
handler.has()
方法是针对 in
操作符的代理方法。
语法
js
var p = new Proxy(target, {
has: function(target, prop) {
}
});
参数
下面是传递给 has
方法的参数。this
is bound to the handler.
target
:目标对象。
prop
:需要检查是否存在的属性。
返回值
has
方法返回一个 boolean 属性的值。
作用于
这个钩子可以拦截下面这些操作:
- 属性查询:
foo in proxy
- 继承属性查询:
foo in Object.create(proxy)
with
检查: with(proxy) { (foo); }
Reflect.has()
代码示例
js
let person = {
name: '张三',
age: 18,
};
let personProxy = new Proxy(person, {
has: function (target, prop) {
console.log('has', prop);
return Reflect.has(target, prop);
}
});
console.log('name' in personProxy);
console.log('age' in personProxy);
console.log('height' in personProxy);
输出内容
has name
true
has age
true
has height
false
handler.isExtensible()
描述
handler.isExtensible()
方法用于拦截对对象的 Object.isExtensible()。
Object.isExtensible()
静态方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
语法
js
var p = new Proxy(target, {
isExtensible: function(target) {
}
});
参数
下列参数将会被传递给 isExtensible
方法。this 绑定在 handler 对象上。
target
:目标对象。
返回值
isExtensible
方法必须返回一个 Boolean 值或可转换成 Boolean 的值。
作用于
该方法会拦截目标对象的以下操作:
代码示例
js
let person = {
name: '张三',
age: 18,
};
let personProxy = new Proxy(person, {
isExtensible: function (target) {
console.log('isExtensible');
return Reflect.isExtensible(target);
},
});
console.log(Object.isExtensible(personProxy));
// 关于 Object.isExtensible 一些知识点
// 新对象是可拓展的。
const empty = {};
Object.isExtensible(empty); // true
// 它们可以变为不可拓展的
Object.preventExtensions(empty);
Object.isExtensible(empty); // false
// 根据定义,密封对象是不可拓展的。
const sealed = Object.seal({});
Object.isExtensible(sealed); // false
// 根据定义,冻结对象同样也是不可拓展的。
const frozen = Object.freeze({});
Object.isExtensible(frozen); // false
输出内容
isExtensible
true
handler.ownKeys()
描述
handler.ownKeys()
方法用于拦截 Reflect.ownKeys()
.
语法
js
var p = new Proxy(target, {
ownKeys: function(target) {
}
});
参数
下面的参数被传递给 ownKeys。this
被绑定在 handler
上。
target
:目标对象。
ownKeys
:方法必须返回一个可枚举对象。
返回值
isExtensible
方法必须返回一个 Boolean 值或可转换成 Boolean 的值。
作用于
该拦截器可以拦截以下操作::
代码示例
js
let person = {
name: '张三',
age: 18,
};
let personProxy = new Proxy(person, {
ownKeys: function (target) {
return Reflect.ownKeys(target);
}
});
console.log(Object.getOwnPropertyNames(personProxy));
输出内容
['name', 'age']
handler.preventExtensions()
描述
handler.preventExtensions()
方法用于设置对Object.preventExtensions()
的拦截
语法
js
var p = new Proxy(target, {
preventExtensions: function(target) {
}
});
参数
以下参数传递给 preventExtensions
方法。它会绑定到这个 handler.
target
:目标对象。
返回值
preventExtensions
方法返回一个布尔值。
作用于
这个 trap 可以拦截这些操作:
代码示例
js
let person = {
};
let personProxy = new Proxy(person, {
preventExtensions: function (target) {
console.log('preventExtensions');
return Object.preventExtensions(target);
},
});
console.log(Object.preventExtensions(personProxy));
输出内容
preventExtensions
{}
handler.set()
描述
handler.set()
方法是设置属性值操作的捕获器。
语法
js
const p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
参数
以下是传递给 get 方法的参数,this 上下文绑定在
handler 对象上。
target
:目标对象。
property
:将被设置的属性名或 Symbol
。
value
:新属性值。
recevier
:最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。
假设有一段代码执行
obj.name = "jen"
,obj
不是一个 proxy,且自身不含name
属性,但是它的原型链上有一个 proxy,那么,那个 proxy 的set()
处理器会被调用,而此时,obj
会作为 receiver 参数传进来。
返回值
set()
方法应当返回一个布尔值。
作用于
该方法会拦截目标对象的以下操作:
- 指定属性值:
proxy[foo] = bar
和proxy.foo = bar
- 指定继承者的属性值:
Object.create(proxy)[foo] = bar
Reflect.set()
代码示例
js
let person = {
};
let personProxy = new Proxy(person, {
set: function (target, prop, value, receiver) {
console.log(`property set ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
});
personProxy.name = '张三';
personProxy.age = 18;
输出内容
property set name = 张三
property set age = 18
handler.setPrototypeOf()
描述
handler.setPrototypeOf()
方法主要用来拦截 Object.setPrototypeOf()
.
语法
js
var p = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
}
});
参数
以下参数传递给 setPrototypeOf
方法。
target
:目标对象。
prototype
:对象新原型或 null
。
返回值
如果成功修改了[[Prototype]]
, setPrototypeOf
方法返回 true
,否则返回 false
.
作用于
这个方法可以拦截以下操作:
代码示例
js
let person = {
};
let personProxy = new Proxy(person, {
setPrototypeOf(target, newProto) {
console.log('setPrototypeOf');
return Reflect.setPrototypeOf(target, newProto);
}
});
Object.setPrototypeOf(personProxy, Array.prototype);
输出内容
setPrototypeOf
总结
- Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等);
- target 需要被代理的对象,handler 包含了很多 traps(拦截器)称为处理对象,traps 有
get
、set
、has
等; - 代理对象可以用于输出一些关键信息,例如代码中调用了哪些对象的属性、函数等;