什么是proxy?
数组问题
数组原length为m,当重新设置数组的length为 n,会自动移除数组的最末m-n个元素,只保留起始处的n个元素。
举个例子,如下:
在proxy之前,我们无法去模拟数组的这种行为。
什么是Prxoy(代理)和Reflect(反射)?
proxy 是一种封装,能去拦截并改变js引擎的底层操作,比如一些不可枚举、不可写入的属性。
通过调用new Proxy(),可以创建一个代理去替代另一个对象(目标对象),
这时,代理对目标对象进行了虚拟,因此,该代理和目标对象在表面上可以当做统一对象来看。
Proxy代理允许拦截目标对象的底层操作,而这本来是js引擎的内部操作。
拦截的行为是个函数,可以修改js对象的内置行为,用于响应拦截的特定操作,我们称为陷阱。
Reflect对象是给底层操作提供默认行为的方法的集合,这些操作可以被proxy代理重写。
每个代理陷阱都有一个对应的反射方法,每个方法与对应的陷阱函数同名,接受的参数也类似。
如果要是使用原先的内置行为,则可以使用对应的反射接口方法。
将代理陷阱和反射方法做了个统一表格,如下:
代理陷阱Proxy |
被重写的行为 |
默认行为Reflect |
get |
读取一个属性的値 |
Reflect.get() |
set |
写入一个属性 |
Reflect.set() |
has |
in运算符 |
Reflect.has() |
deleteProperty |
delete 运算符 |
Reflect.deleteProperty() |
getPrototypeOf |
Object.getPrototypeOf() |
Reflect.getPrototypeOf() |
setPrototypeOf |
Object.setPrototypeOf() |
Reflect.setPrototypeOf() |
isExtensible |
Object.isExtensible() |
Reflect.isExtensible() |
preventExtensions |
Object.preventExtensions() |
Reflect.preventExtensions() |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor() |
Reflect.getOwnPropertyDescriptor() |
defineProperty |
Object.defineProperty() |
Reflect.defineProperty() |
ownKeys |
Object.keys() Object.getOwnPropertyNames()与 Object.getOwnPropertySymbols() |
Reflect.ownKeys() |
apply |
调用一个函数 |
Reflect.apply() |
construct |
使用new调用一个函数 |
Reflect.construct() |
创建代理
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陷阱函数
let target = {} console.log(target.name) // 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 = l console.log(proxy.count) // l console.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: 42 } let proxy = new Proxy(target, { has(trapTarget, key) { if (key === "value") { return false } else { return Reflect.has(trapTarget, key); } } }) console.log("value" in proxy); // false console.log("name" in proxy); // true console.log("tost ring" in proxy); // true
使用了 has陷牌函数,用于检查key値是否为"value"。如果是,则 返回false,否则通过调用Reflect.has()方法来返回默认的结果。