ES6代理和反射新特性,详细讲解

代理与反射

es6新增了代理和反射特性,这两个特性为开发者提供了拦截并向基本操作嵌入额外行为的能力。

代理基础

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Title</title>
  </head>
  <body>
    <h2>代理与反射</h2>
  </body>
  <script>
    const target = {
      id: 'target'
    }
    const handle = {}
    const proxy = new Proxy(target,handle)
    console.log(proxy.id)
    console.log(target.id)
  </script>
</html>

代理是目标对象的抽象。 所以直接操作代理和直接操作对象,所操作的值都会映射到代理对象上。

代理对象每次执行 某个操作(读属性、写属性、定义新属性、查询原型、把它作为函数调用)时,它只会把相应操作发送给处理器对象或目标对象。

Proxy构造器接收两个参数,目标对象处理器对象 。如果处理器对象上存在 对应方法,代理就调用该方法执行相应操作。如果处理器对象上不存在对应方法,则代理就在目标对象上执行基础操作。

定义捕获器

使用代理的主要目的是可以定义捕获器。当定义捕获器后,在代理对象上调用基本操作时(比如新增、删除、修改),代理可以在运行这些操作之前调用捕获器,从而拦截并修改相应的行为。

下面看一个例子

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Title</title>
</head>
<body>
  <h2>代理与反射</h2>
</body>
<script>
  const target = {
      id: 'target'
  }
  const handle = {
      get(){
          console.log('捕获器运行')
          return '这是一个捕获器'
      }
  }
  const proxy = new Proxy(target,handle)
  console.log(proxy.id)
  console.log(target.id)
</script>
</html>

在访问代理对象的属性时,会出发get操作(捕获器函数),但是在原始对象上操作是不会触发的。否则就应该打印两次了。

捕获器函数可以接收三个参数,分别是目标对象,要查询的属性以及代理对象。

那如果我们想在捕获函数内部调用原始对象上的行为呢?Reflect对象为我们提供了这个能力。

下面看一个例子

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Title</title>
</head>
<body>
  <h2>代理与反射</h2>
</body>
<script>
  const target = {
      foo: 'bar'
  }
  const handle = {
      get(){
          return Reflect.get(...arguments)
      }
  }
  const proxy = new Proxy(target,handle)
  console.log(proxy.foo)
  console.log(target.foo)
</script>
</html>

可以看到值是一样的,那么我们利用这个属性就可以在操作对象时给对象的属性添加一些额外的东西,比如

javascript 复制代码
const target = {
    foo: 'bar'
}
const handle = {
    get(){
        return Reflect.get(...arguments) + '帅'
    }
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)
捕获器的一些限制

在使用捕获器时,需要遵守"捕获器不变式 "。原因是因为:如果一个捕获器不遵守捕获器不变式,可能会出现过于反常的行为! 下面来看一个例子:

如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出TypeError

javascript 复制代码
const target = {

}
Object.defineProperty(target,'foo',{
    // 不可配置
    configurable: false,
    // 不可写
    writable: false,
    value: 'bar'
})
const handle = {
    get(){
        return Reflect.get(...arguments) + '帅'
    }
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)

那捕获器函数返回一个一样的值呢?

实测返回一样的值不会报错。

撤销代理

很简单,调用一个revocable方法。有想要了解具体的可以看看MDN的介绍。

Proxy.revocable() - JavaScript | MDN

看一段代码就知道怎么使用了

javascript 复制代码
const target = {

}
Object.defineProperty(target,'foo',{
    // 不可配置
    // configurable: false,
    // // 不可写
    // writable: false,
    value: 'bar'
})
const handle = {
    get(){
        console.log('运行')
        return Reflect.get(...arguments)
    }
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)
const {proxy:proxy1,revoke} = Proxy.revocable(target,handle)
proxy1.foo
revoke()
proxy1.foo
代理的不足
  1. this指向问题

  2. 代理与内部槽位

    1. 有些js内置类型可能会依赖内部槽位进行一些方法的调用,而代理对象是无法访问到这些方法的,就会导致出错。
javascript 复制代码
const target = new Date()
const proxy = new Proxy(target,{})
console.log(proxy instanceof Date)
proxy.getDate()

输出结果:

反射

Reflect对象是一个内置的对象,不可以使用new运算符进行实例化,它可以用自己的方法代替原生的操作对象。就是有一个人和你一模一样,你可以不用亲自做一些事情,这个人就帮你做了。

来看一下例子:

javascript 复制代码
const target = {
    foo: 'bar'
}
delete target.foo
console.log(target)

这是我们如果想要删除对象上的某个属性,采取的一种做法。

来看下反射如何做

javascript 复制代码
const target = {
    foo: 'bar'
}
Reflect.deleteProperty(target,'foo')
console.log(target)

在学习过程中我是有一个疑惑的,为什么JS会出现反射呢? 其实它是为了跟Proxy进行配合。

Proxy可以代理对象的一些操作,比如增加、删除、修改。想一下这个场景:如果Proxy代理了新增操作,然后最后想调用原生的赋值方法,该怎么做?

  1. 自己手动实现一个。
javascript 复制代码
const target = {
    foo: 'bar'
}
let proxy1 = new Proxy(target,{
    get(target, p, receiver) {
        console.log('当前值为::',target[p])
        return target[p]
    }
})
console.log(proxy1.foo)
  1. 使用反射
javascript 复制代码
const target = {
    foo: 'bar'
}
let proxy1 = new Proxy(target,{
    get(target, p, receiver) {
        console.log('当前值为::',target[p])
        return Reflect.get(target,p,receiver)
    }
})
console.log(proxy1.foo)

我们无需关心原来操作的实现逻辑,只需要关心我们要为某个操作添加的逻辑即可,原来的逻辑反射会替我们做到。

所以反射的Api是和Proxy捕获器的api一一对应的,当我们想要调用原生操作时,直接使用反射提供的api即可。

总结

从宏观来看,代理是真实JS对象的透明抽象层。在遵循捕获器不变式的前提下,代理可以定义捕获器,可以拦截大部分JS的基本操作和方法。从而增强这些操作。 例如实现装饰器。

反射则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射API看作一套基本操作,包含绝大部分JS对象的API基础。

相关推荐
学习前端的小z2 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁25 分钟前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜25 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40426 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish27 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple27 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five28 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序28 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54129 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普30 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5