原来是【代理】和【反射】哇!!!

367 阅读5分钟

有什么用?

提供了拦截并向基本操纵嵌入额外行为的能力。

可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。  在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

基本使用

创建空代理

new Proxy(目标对象,处理程序对象)

  • 返回一个代理对象
  • 两个参数缺一不可,缺少一个会报错
//定义了两个对象
const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象

}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

测试:

console.log(obj1.name)//拉妮
console.log(test.name)//拉妮

obj1.name = '娇小拉妮'
console.log(obj1.name)//娇小拉妮
console.log(test.name)//娇小拉妮

test.name = '娇小拉妮!!!!!'
console.log(obj1.name)//娇小拉妮!!!!!
console.log(test.name)//娇小拉妮!!!!!
  1. 不管是对目标对象的操作还是对代理对象的操作,都会反应到两个对象身上。

  2. 目标对象和代理对象进行严格比较(===),并不相等。

Proxy.prototypeundefined,因此不可以对其使用 instanceof 操作符,会报错

console.log(target instanceof Proxy); // 报错
console.log(proxy instanceof Proxy); // 报错

定义捕获器

每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。

实际是通过代理对象对于目标方法各类操作和行为的拦截

例:

//定义了两个对象
const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    get(){//get捕获器
        console.log('被读取!!')
    },
    set(){//set捕获器
        console.log('试图将其修改为'+arguments[2])
    }
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

test.name//被读取!!
test.name = '娇小拉妮!'//试图将其修改为娇小拉妮!

反射-Reflect

  • Reflect-封装了原始行为

  • 作用:简化基于自己的参数重建原始行为的工作

使用捕获的部分方法

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    get(){
        return Reflect.get(...arguments)
    },
    //get:Reflect.get //---简易写法
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

捕获所有方法,并将每个方法转发给对应反射API的空代理

即:使用捕获的全部方法

const obj1 = {//用作目标对象
    name:'拉妮'
}

//test实现了对obj1的代理
const test = new Proxy(obj1,Reflect)

捕获不变式

使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。

捕获器不变式(trap invariant) 因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。比如说,目标对象有一个不可配置且不可写的数据属性, 那么捕获器在返回一个与该属性不同的值时,就会抛出错误。

可撤销代理

不是使用new Proxy()创建,而是使用Proxy.revocable()创建并暴露出一个revoke()方法实现代理撤销。

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    get:Reflect.get
}

//test实现了对obj1的代理
const {proxy,revoke} = Proxy.revocable(obj1,obj2)

console.log(proxy.name)//拉妮

revoke()//使用revoke()撤销代理----此操作是幂等的,调用多少次结构都一样

// console.log(proxy.name)//撤销代理之后再使用就会报错

使用反射API的好处

object上的方法适用于通用程序,反射方法则适用于更加精细与协调的对象控制与操作。

状态标记

用一等函数代替操作符

方法名替代
Reflect.get可替代对象访问操作符 .
Reflect.set可替代 = 赋值操作符
Reflect.has可替代 in 操作符 和 with()
Reflect.deleteProperty可替代 delete 操作符
Reflect.construct可替代 new 操作符

安全地应用函数

  • 设想一个场景,假设我们在通过 apply 函数调用函数时,被调用的函数自身也定义了自己的 apply 属性,那么调用的结果肯定不是我们所期望的。这种问题可以通过使用定义在 Function 原型上的 apply 方法来解决。
Function.prototype.apply.call(Fun,thisVal,args)
复制代码
  • 但是更好的解决方式则是使用反射API Reflect.apply 来解决
Reflect.apply(Fun,thisVal,args)

代理另一个代理

多层代理-->多层拦截网

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    get:Reflect.get
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

const obj3 = {//用作处理程序对象
    get:Reflect.get
}

//test2实现了对test的代理
const test2 = new Proxy(test,obj3)

console.log(test2.name)//拉妮

代理存在的问题

  • 代理中的this值可能会存在问题

  • Date类型与代理不兼容---内置槽位不协同

部分反射方法

get()

读取属性值。

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    get(){//get接收三个参数:目标对象,属性,代理对象
        console.log(...arguments)
    }
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

test.name
//{ name: '拉妮' } name { name: '拉妮' }

set()

修改属性值。

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    set(){//接收到四个参数:目标对象,属性,接收的值,接收赋值的对象
        console.log(...arguments)
    }
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

console.log(test.name = '娇小拉妮') 
//{ name: '拉妮' } name 娇小拉妮 { name: '拉妮' }

has()

在in操作符中被调用。

deleteProperty()

使用delete删除属性值时的调用。

const obj1 = {//用作目标对象
    name:'拉妮'
}
const obj2 = {//用作处理程序对象
    deleteProperty(){//接收到两个参数:目标对象,属性
        console.log('检测到删除操作!!')
    }
}

//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)

delete test.name
//检测到删除操作!!

construct()

在使用new操作符时被调用。

ownKeys()

在使用Object,keys()及类似方法中被调用。

apply()

在调用函数时被调用。

。。。。。。

代理模式

代理的应用

跟踪属性访问

通过捕获get,set,has等操作,实现对于对象的监视。

隐藏属性

通过get和has实现对于目标对象属性的隐藏。

属性验证

赋值操作都会触发set捕获器,所有可以在set内部控制是否可以更改。

函数与构造函数参数验证

在apply拦截器内部验证参数,如控制函数只接收某一种类型的值。

数据绑定与可观察对象

通过代理把运行中原本不相关的部分联系到一起,实现不同代码间的相互操作。