什么是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陷阱函数

读取对象不存在的属性,会显示undefined,而不会报错。
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 = 1set陷阱函数被调用此时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()方法来返回默认的结果。

posted @ 2020-02-12 14:55  灰姑娘的冰眸  阅读(1408)  评论(0编辑  收藏  举报