Proxy代理和反射

代理基础:

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

用作目标对象的替身,但又完全独立于目标对象。目标对象既可以直接被操作,也可以通过代理来操作。

创建代理

代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺 少其中任何一个参数都会抛出 TypeError。

默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。

const target = { // ! 目标对象
            id: 'target'
        }
        const handler = {} // ! 处理程序对象
        const proxy = new Proxy(target, handler)
        
        // id 属性会访问同一个值
        console.log(target.id); // => target 
        console.log(proxy.id); // => target 

        // 给目标属性赋值会反映在两个对象上
        // 因为两个对象访问的是同一个值
        target.id = 'foo'; 
        console.log(target.id); // => foo 
        console.log(proxy.id); // => foo 

        // 给代理属性赋值会反映在两个对象上
        // 因为这个赋值会转移到目标对象
        proxy.id = 'bar'; 
        console.log(target.id); // => bar 
        console.log(proxy.id); // => bar 

console.log(target === proxy); // false

  • target:目标对象

  • handler:处理程序对象

定义捕获器

使用代理的主要目的就是定义捕获器,主要在处理程序对象中创建“拦截器”。

在操作系统中,捕获器是程序流中的一个同步中断,可以暂停程序流,转而执行一段子例程,之后再返回原始程序流。

定义一个 get()捕获器

const target = { // 目标对象
            id: 'target'
        }
        const handler = { // 处理程序对象
            get() {
                return target change!!!
            }
        }
        const proxy = new Proxy(target, handler)
        console.log(target.id) // => target
        console.log(proxy.id)  // => target change!!!

        console.log(target['foo']); // => target
        console.log(proxy['foo']); // => target change!!!

        console.log(Object.create(target)['foo']); // => target 
        console.log(Object.create(proxy)['foo']); // => target change!!!

捕获器在处理程序对象中以方法名为键

1.当通过代理对象执行 get()操作时,就会触发定义的 get()捕获器。
2.proxy[property]、proxy.property 或 Object.create(proxy)[property]等操作都 会触发基本的 get()操作以获取属性。
3.只要这些操作作用于代理对象上,就会触发 get()捕获,在目标对象上仍然是正常的行为

捕获器参数

get() 捕获器会接收到目标对象、要查询的属性和代理对象三个参数,于这些参数可以重建被捕获方法的原始行为。

const target = {
            id: 'target'
        }
        const handler = {
            get(IsTarget, property, receiver ) {
                console.log(IsTarget === target) // => true
                console.log(property) // => id
                console.log(receiver=== proxy) // => true
                
                /**
                 * 有了这些参数,我们可以重新处理捕获行为
                 */
                return IsTarget[property]
            }
        }
        const proxy = new Proxy(target, handler)
        console.log(proxy.id) // => target
        //执行结果
  • IsTarget:目标对象

  • property: 属性

  • receiver:代理对象

反射(Reflect)API 方法

捕获器都可以基于自己的参数重建原始操作,实际上、 开发者并不需要手动重建元素行为,而是可以通过调用全局 Reflect 对象(封装了原始行为)的同名方法重建。

const target = {
            id: 'target'
        }
        const handler = { 
            get: Reflect.get 
        }; 
        const proxy = new Proxy(target, handler)
        console.log(target.id) // => target
        console.log(proxy.id)  // => target

捕获器不变式

根据 ECMAScript 规范,每个 捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式” (trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。

如果目标对象有一个不可配置不可写的数据属性,那么捕获器会抛出TypeErroe

可撤销代理

Proxy.revocable()创建一个可撤销的Proxy对象。

作用:中断代理对象和目标对象之间的联系。(new Proxy() 创建的普通代理会在代理对象的什么周期一直持续存在)

const target = {
          id: 'target'
      }
      const handler = {
          get(IsTarget, property, receiver ) {
              return 'handler!!!'
          }
          // get: Reflect.get
      }
      // ! Proxy.revocable() 可撤销代理对象与目标对象的关联。
      const { proxy, revoke } = Proxy.revocable(target, handler)
      console.log(target.id) // => target
      console.log(proxy.id)  // => handler!!!
      revoke() // ! 撤销代理
      console.log(proxy.id) // => TypeError

这种操作是不可逆的

用一个代理去代理另一个代理

 target = {
            id: 'target'
        }
        const handler = {
            get(IsTarget, property, receiver ) {
                console.log('firstProxy')
                return Reflect.get(...arguments)
            }
        }
        const firstProxy = new Proxy(target, handler)

        const endProxy = new Proxy(firstProxy, {
            get(IsTarget, property, receiver ) {
                console.log('endProxy')
                return Reflect.get(...arguments)
            }
        })
        console.log(endProxy.id)
        // endProxy
        // firstProxy
        // target

get()

  • get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。

  • get() 返回值无限制,

  • target: 目标对象

  • property:目标对象的字符串键值

  • receiver:代理对象或者继承代理对象的对象

set()

set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

const target = {}

        const proxy = new Proxy(target, {
            set(target, property, value, receiver) {
                console.log('set()')
                return Reflect.set(...arguments)
            }
        })
        console.log(proxy.name = '张三')

// set()
// 张三
  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

  • value:新属性值。

  • receiver:接收最初赋值的对象。

  • 返回值:返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError

  • 如果 target.property 不可写且不可配置,则不能修改目标属性的值

has()

handler.has() 方法是针对 in 操作符的代理方法。

in: 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true

const target = {}

       const proxy = new Proxy(target, {
           has(target, prop) {
               console.log('has()')
               return Reflect.has(...arguments)
           }
       })
       console.log('name' in proxy) // false
  • target:目标对象.

  • prop:需要检查是否存在的属性.

  • has has()必须返回布尔值,表示属性是否存在。

apply()

handler.apply() 方法用于拦截函数的调用。

function sum(a, b) {
        return a + b;
        }

        const handler = {
        apply: function(target, thisArg, argumentsList) {
            console.log(`apply ${argumentsList}`);
            return target(argumentsList[0], argumentsList[1]) * 10;
        }
        };

        const proxy = new Proxy(sum, handler);

        console.log(sum(1, 2)); // => 3
        console.log(proxy(1, 2)); // => 30
  • target:目标对象(函数)。

  • thisArg:被调用时的上下文对象。

  • argumentsList:被调用时的参数数组。

  • 返回值:apply方法可以返回任何值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容