• proxy


    什么是proxy

    proxy翻译过来就是代理的意思,那么在javascript中,proxy(代理)是什么意思呢?proxy时ES6提供的新的API,可以用来定义对象的各种基本操作。proxy是一种可以拦截并改变底层javascript引擎操作的包装器,在新语言中通过它暴露内部运作的对象。

    数组的问题

    在ES6出现以前,开发者不能通过自己定义的对象模仿javascript数组对象的行为方式。当给数组的特定元素赋值时,影响到该数组的length属性,也可以通过length属性修改数组元素。例如:

    let colors = ['red', 'green', 'blue']
    console.log(colors.length) //3
    colors[3] = "black"
    console.log(colors.length) //4
    console.log(colors[3]) //'black'
    colors.length = 2
    console.log(colors.length) //2
    console.log(colors[3]) //undefined
    console.log(colors[2]) //undefined
    console.log(colors[1]) //'green'
    

    colors数组一开始有3个元素,将colors[3]赋值为'black'的时候,数组的length自动增加到4,将length设置为2的时候会移除数组的后两个元素而只保留前两个。在es5之前开发者无法实现这些行为,但是现在通过代理就可以了

    注意:数值属性和length属性具有这种非标准行为,因而在es6中数组被认为是奇异对象

    代理和反射

    调用new proxy()可创建代替其他目标(target)对象的代理,它虚拟花了目标,所以二者看起来功能一致

    代理可以拦截javascript引擎内部目标的底层兑现该操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数

    反射API以Reflect对象的形式出现,对象中方法的默认特性与相同的底层操作一致,而代理可以复写这些操作,每个代理陷阱对应一个命名和参数都相同的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.getOwnPertyDescriptor() | Reflect.getOwnPertyDescriptor() |
    | defineProperty | Object.defineProperty() | Reflect.defineProperty() |
    | OwnKeys | Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbol() | Reflect.OwnKeys() |
    | apply | 调用一个函数 | Reflect.apply() |
    | construct | 用new调用一个函数 | Reflect.construct |
    每个陷阱覆写javascript对象的一些特性,可以用他们拦截并修改这些特性。如果仍需要使用内建特性,则可以使用相应的反射API方法。创建代理会让代理和反射API的关系变得清楚,所以我们最好深入进去看一些示例

    创建一个简单的代理

    用Proxy构造函数创建代理需要传入两个参数:目标(target)和处理程序(handler)。处理程序时定义一个或多个陷阱的对象,在代理中,除了专门为操作定义的陷阱外,其余操作均使用默认特性。不适用任何陷阱的处理程序等价于简单的转发代理,就像这样

    let target = {}
    let proxy = new Proxy(target,{});
    proxy.name = 'proxy'
    console.log(proxy,proxy.name)
    console.log(target,target.name)
    console.log(proxy == target)
    target.name = 'target'
    console.log(proxy,proxy.name)
    console.log(target,target.name)
    

    得到的结果如图所示

    这个示例中的代理将所有操作直接转发到目标,将"proxy"赋值给proxy.name属性时,会在目标上创建name,代理只是简单地将操作转发给目标,它不会储存这个属性。由于proxy.name和target.name引用的都是target.name,因此二者的值相同,从而target.name设置新值后,proxy.name也一同变化

    使用set陷阱验证属性

    假设你想创建一个属性值是数字的对象,对象中每新增一个属性都要加以验证,如果不是数字必须抛出错误。为了实现这个任务,可以定义一个set陷阱来覆写设置值的默认特性。set陷阱接受4个参数
    1.trapTarget 用于接收属性(代理的目标)的对象
    2. key被写入的属性键(字符串或Symbol类型)
    3. value被写入属性的值
    4. receiver 操作发生的对象(通常是代理)
    Reflect.set()是set陷阱对应的反射方法和默认特性,它和set代理陷阱一样接收相同的4个参数,以方便在陷阱中使用。如果属性已设置陷阱应该返回true,如果未设置则返回false。(Reflect.set()方法基于操作是否成功来返回恰当的值)
    可以使用set陷阱并检查传入的值来验证属性值,例如

     let target = {
            name: 'target'
        }
        let proxy = new Proxy(target, {
                set(trapTarget, key, value, receiver) {
                    //忽略不希望受到影响的已有属性
                    if (!trapTarget.hasOwnProperty(key)) {
                        console.log(value, isNaN(value))
                        if (isNaN(value)) {
                            throw new TypeError("属性必须是数字")
                        }
                    }
                    //添加属性
                    return Reflect.set(trapTarget, key, value, receiver)
                }
            })
            // 添加一个新属性
        proxy.count = 1
        console.log(proxy.count)
        console.log(target.count)
            //由于目标已有name属性因而可以为他赋值
        proxy.name = 'proxy'
        console.log(proxy.name)
        console.log(target.name)
            //给不存在的属性赋值会抛出错误
        proxy.anotherName = 'proxy'
    

    结果如下图所示

    这段代码定义了一个代理来验证添加到target的新属性,当执行prox.count = 1时,set陷阱被调用,此时trapTarget的值等于target,key为"count",value的值等于1,reciever(本例中未使用)等于proxy。由于target上没有count属性,因此代理继续将value值传入isNaN(),如果结果时NaN,则证明传入的属性值不是数字,同时也抛出一个错误。这段代码中,count被设置为1,所以代理调用Reflect.set()方法并传入陷阱接收的4个参数来添加新属性

    proxy.name可以成功的赋值为一个字符串,这是因为target已经拥有一个name属性了,但通过调用trapTarget.hasOwnProperty()方法验证检查后被排除了,所以目标已有的非数字属性仍然可以被操作。然后,将proxy.anotherName赋值为一个字符串时会抛出错误。目标没有anotherName属性,所以它的值时需要被验证,而由于"proxy"不是一个数字值,因此抛出错误。

    set代理陷阱可以拦截写入属性的操作,get代理陷阱可以拦截读取属性的操作

    用get陷阱验证对象结构(Object Shape)

    javascript有一个时常令人感到困惑的特殊行为,即读取不存在的属性时不会抛出错误,而是用undefined代替被读取属性的值,就像在这个示例中

    let target = {}
    console.log(target.name)//undefined
    

    在大多数语言中,如果target没有name属性,尝试读取target.name会抛出一个错误,但是javascript却用undefined来代替target.name属性的值。如果你曾接触过大型代码库,应该知道这个特性会导致重大问题,特别是当错误输入属性名称的时候,而代理可以通过检查对象的结构来帮助你回避这个问题。

    对象结构是指对象中所有可用的属性和方法的集合,javascript引擎通过对象结构来优化代码,通常会创建类来表示对象,如果你可以安全地假定一个对象将始终具有相同的属性和方法,那么当程序试图访问不存在的属性时会抛出错误,这对我们很有帮助。代理让对象结构变得简单
    因为只有当读取属性时,才回检测属性,所以无论对象中是否存在某个属性,都可以通过get陷阱来检测,它接收3个参数

    1. trapTarget 被读取属性的源对象(代理的目标)
    2. key 要读取的属性键(字符串或者symbol)
    3. receiver 操作发生的对象(通常是代理)

    由于get陷阱不写入值,所以它复刻了set陷阱中除value外的其他3个参数,Reflect.get()也接受同样3个参数并返回属性的默认值。

    如果属性在目标上不存在,则使用get陷阱和Reflect.get()时会抛出错误,就像这样:

    let proxy = new Proxy({}, {
          get(trapTarget, key, receiver) {
          if (!(key in receiver)) {
            throw new TypeError("属性" + key + "不存在")
    
          }
          return Reflect.get(trapTarget, key, receiver)
      }
    })
    //新添一个属性,程序仍然正常运行
    proxy.name = 'proxy'
    console.log(proxy.name) // proxy
        //如果属性不存在,则抛出错误
    console.log(proxy.nme) //抛出错误     
    

    此示例中的get陷阱可以拦截属性的读取操作,并通过in操作符来判断receiver上是否具有被读取的属性,这里之所以用in操作符检查receiver而不检查trapTarget,是为了放置receiver代理含有has陷阱。在这种情况下检查trapTarget可能会忽略掉has陷阱,从而得到错误的结果。属性如果不存在会抛出一个错误,否则就是用默认行为。

    这段代码展示了如何在没有错误的情况下给proxy添加新属性name,并写入值和读取值。最后一行包含一个输入错误:proxy.nme有可能是proxy.name,由于nme时一个不存在的属性,因为抛出错误

  • 相关阅读:
    Objective-C 资源收藏
    坑爹的高德地图API
    nginx的location root alias指令以及区别
    Java Swing 界面中文乱码问题解决(Idea环境)
    不同的国家/地区与语言缩写代码
    IDEA编译时出现 Information:java: javacTask: 源发行版 1.8 需要目标发行版 1.8
    请重视!请重视!请重视!百度熊掌号之搜索资源平台体验
    php7带来的性能升级
    详解Asp.Net Core 2.1+的视图缓存(响应缓存)
    Xshell5 提示要继续使用此程序,您必须应用最新的更新或使用新版本
  • 原文地址:https://www.cnblogs.com/dehenliu/p/14094707.html
Copyright © 2020-2023  润新知