什么是proxy?-飞外

数组问题

数组原length为m,当重新设置数组的length为 n,会自动移除数组的最末m-n个元素,只保留起始处的n个元素。

举个例子,如下:

在proxy之前,我们无法去模拟数组的这种行为。

什么是Prxoy(代理)和Reflect(反射)?

proxy 是一种封装,能去拦截并改变js引擎的底层操作,比如一些不可枚举、不可写入的属性。

通过调用new Proxy(),可以创建一个代理去替代另一个对象(目标对象),

这时,代理对目标对象进行了虚拟,因此,该代理和目标对象在表面上可以当做统一对象来看。

Proxy代理允许拦截目标对象的底层操作,而这本来是js引擎的内部操作。

拦截的行为是个函数,可以修改js对象的内置行为,用于响应拦截的特定操作,我们称为陷阱。

Reflect对象是给底层操作提供默认行为的方法的集合,这些操作可以被proxy代理重写。

每个代理陷阱都有一个对应的反射方法,每个方法与对应的陷阱函数同名,接受的参数也类似。

如果要是使用原先的内置行为,则可以使用对应的反射接口方法。

将代理陷阱和反射方法做了个统一表格,如下:


Object.keys()

Object.getOwnPropertyNames()与 Object.getOwnPropertySymbols()


var proxy = new Proxy(target, handler);

参数:target参数表示所要拦截的目标对象,

handler参数也是一个或多个陷阱函数的对象,用来定制拦截行为。若没有提供陷阱函数,则代理采取默认行为操作。

new Proxy( ) 表示生成一个Proxy实例

let target = {}let proxyObj = new Proxy(target, {})proxyObj.name = "proxyName"console.log(proxyObj.name) // Proxy {name: "proxyName"} console.log(target.name) // {name: "proxyName"} console.log(proxyObj.name) //proxyName console.log(target.name) //proxyName target.name = "targeName" console.log(proxyObj.name) //targeName console.log(target.name) //targeName

proxy对象将属性赋值的操作传递给target对象;

为target.name设置属性值,也会在proxy.name上有相同的改变。

get陷阱函数读取对象不存在的属性,会显示undefined,而不会报错。

get陷阱函数在读取属性时被调用,即使对象不存在此属性,也可以接受参数。

get陷阱函数有三,分别为

trapTarget:被读取属性的对象(代理的目标对象)key:被读取属性的键receiver:操作发生的对象(代理的对象)

注:1)Reflect.get()方法 接受参数和get陷阱函数相同。

2)set陷阱函数的参数有四个(trapTarget、key、value、receiver), 而get陷阱函数没有使用value参数,是因为get陷阱函数不需要设置属性值。

举个例子来具体说明下:

eg: 读取目标属性不存在的情况下,报错

var target = { name : 'targetName'let proxy = new Proxy(target, { get(trapTarget, key, receiver) { if (!(key in trapTarget)) { throw new TypeError("属性" + key + " doesn't exist."); return Reflect.get(trapTarget, key, receiver);console.log(proxy.name) // "targetName"// 添加属性的功能正常proxy.place = "北京";console.log(proxy.place) // "北京"// 读取不存在属性会报错console.log(proxy. sex) // 报错

由于我们是读取对象的属性,只需要使用get陷阱函数。

在本例中,通过in运算符来判断receiver对象上是否存在已有的属性,从而进行拦截操作。

以上看出,可以添加属性且能够读取存在的属性,而读取不存在属性会报错。

set陷阱函数

set陷阱函数有四个参数,分别为

trapTarget:被接受属性的对象(代理的目标对象)key:被写入属性的键value:被写入属性的值receiver:操作发生的对象(代理的对象)

set()在写入属性成功返回true,否则返回false。

同样的,Reflect.set()参数和set陷阱函数一致,且Reflect.set()依据操作的不同返回相应的结果。

举个例子来说明下,

eg: 创建对象,且属性值只能是num类型,若类型不符,则报错。需要用set陷阱函数去重新属性值的默认行为。

let target = { name: "target"let proxy = new Proxy(target, { set(trapTarget, key, value, receiver) { console.log(trapTarget, key, value, receiver)  // proxy.count = 1 打印结果为 {name: "target"} "count" 1 Proxy{name: "target"}  // proxy.name = "proxyName” 打印结果为 {name: "target", count: 1} "name" "proxyName" Proxy{name: "target", count: 1}  // 忽略已有属性,避免影响它们 if(!trapTarget.hasOwnProperty(key)) { if(isNaN(value)) { throw new TypeError("Property must be a number."); // 添加属性 return Reflect.set(trapTarget, key, value, receiver);// 添加一个新属性proxy.count = lconsole.log(proxy.count) // lconsole.log(target.count) // l// 你可以为name 赋一个非数值类型的值,因为该属性已存在 proxy.name = "proxy"console.log(proxy.name) // "proxyName"console.log(target.name) // "proxyName"// 抛出错误proxy.anotherName = "proxyOtherName" 

当执行 proxy.count = 1时,set陷阱函数被调用,此时trapTarget的値等于target对象,key的値是字符串"count" ’,value的値是1 。

target对象上尚不存在名为count的属性,因此代理将 value参数传递给isNaN()方法进行验证;

如果验证结果是NaN ,表示传入的属性値不是 一个数値,需要拋出错误;

但由于这段代码将count参数设置为1 ,验证通过,代理使用一致的四个参数去调用Reflect.set()方法,从而创建了一个新的属性。

当proxy.name被赋值为字符串时,操作成功完成。这是因为target对象已经拥有一个 name属性,

因此验证时通过调用trapTarget.hasOwnProperty()会忽略该属性,这就确保允 许在该对象的已有属性上使用非数値的属性値。

当proxy.anotherName被紙値为字符串时,抛出了一个错误。这是因为该对象上并不存在 anotherName属性,因此该属性的値必须被验证,

而因为提供的値不是一个数値,验证过程 就会抛出错误。

has陷阱函数

in 运算符判断对象是否存在某个属性,无论该属性是对象自身属性,还是其原型属性

val是自身属性,toString是 原型属性,

has陷阱函数参数有两个,分别为

trapTarget:需要读取属性的对象(代理的目标对象)key:需要检查的属性的键

Reflect.has()方法接受与之相同的参数,并向in运算符返回默认响应结果。

使用has 陷阱函数以及Reflect.has()方法,允许你修改部分属性在接受in检測时的行为,但保留其他属性的默认行为。

举个例子来说明:

eg: 只想要隐藏value属性

let target = { name: "target", value: 42let proxy = new Proxy(target, { has(trapTarget, key) { if (key === "value") { return false } else { return Reflect.has(trapTarget, key);console.log("value" in proxy); // falseconsole.log("name" in proxy); // trueconsole.log("tost ring" in proxy); // true

使用了has陷牌函数,用于检查key値是否为"value"。如果是,则 返回false,否则通过调用Reflect.has()方法来返回默认的结果。