Proxy 拦截器

如有错误,麻烦指正,共同学习

Proxy的原意是“拦截”,可以理解为对目标对象的访问和操作之前进行一次拦截。提供了这种机制,所以可以对目标对象进行修改和过滤的操作。

    const proxy = new Proxy({}, {
        get(target, proper(Key) {
            console.log('你的访问被我拦截到了')
            return 1;s
        },
        set(target, properKey, properValue) {
            console.log('你修改这个属性被我拦截到了')
        }
    })

Proxy 实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。

语法:
const proxy = new Proxy(target, hanlder)

new Proxy生成一个 proxy的实例, target表示要拦截的目标,可以是对象或函数等。凡是对目标对象的一些操作都会经过拦截器的拦截处理。 hanlder 参数也是一个对象,它表示拦截配置,就如上例所示。

Proxy 实例也可以作为其他对象的原型对象。对这个目标对象进行操作,如果它自身没有设置这个属性,就会去它的原型对象上面寻找,从而出发拦截行为。如下例:

  const proxy = new Proxy({}, {
      get(target, key) {
          consoloe.log(`你访问的属性是${key}`)
      }
  })
  const newObject = Object.create(proxy)
  newObject.a  // 你访问的属性是a

提醒

  • 同一个拦截器可以拦截多个操作,只需要在第二个参数(hanlder)配置添加

  • 如果对这个目标对象没有设置拦截行为,则直接落在目标对象上。

Proxy 支持的拦截操作

  • get(target, propKey, receiver) 拦截对象属性读取
  • set(target, propKey, value, receiver) 拦截对象的属性设置
  • has(target, propKey) 拦截propkey in proxy
  • deleteProperty(target, propKey) 拦截delete proxy[propKey]
  • ownKeys(target)
  • getOwnPropertyDescriptor(target, propKey) 返回对象属性的描述对象拦截
  • defineProperty(target, propKey, propDesc)
  • proventExtensions(target)
  • getPrototypeOf(target)
  • isExtensible(target)
  • setPrototypeOf(target, proto)
  • apply(target, object, args)
  • construct(target, args) 拦截 proxy 实例作为构造函数调用的操作

Proxy 实例的方法

get(target, key): 当访问目标对象属性的时候,会被拦截。

target: 目标对象
key: 访问的key值

   const proxy = new Proxy({a:1,b:2}, {
       get(target, key) {
           console.log('called')
           return target[key]
       }
   })
   proxy.a // 1
   // called 会被打印出来 

上面的代码中,当读取代理对象属性的时候,会被get方法拦截。所以可以在拦截前做一些事情,比如必须访问这个对象存在的属性,如果访问对象不存在的属性就抛出错误! 如下例:

    const obj = {
        name: 'qiqingfu',
        age: 21
    }
    const proxy = new Proxy(obj, {
        get(target, key) {
            if (key in target) {
                return target[key]
            } else {
                throw Error(`${key}属性不存在`)
            }
        }
    })

以上代码读取代理对象的属性,如果存在就正常读取,负责提示错误访问的key值不存在。

如果一个属性不可配置(configurable), 或者不可写(writeble),则该属性不能被代理

    const obj = Object.defineProperties({}, {
        foo: {
            value: 'a',
            writeble: false,  // 不可写
            configurable: false, //不可配置
        }
    })
    const proxy = new Proxy(obj, {
        get(target, key) {
            return 'qiqingfu'
        }
    })
    proxy.value // 报错
场景例子:

通过get()方法可以实现一个函数的链式操作

    const pipe = (function(){
        return function (value) {
            const funcStack = []; // 存放函数的数组
            const proxy = new Proxy({}, {
                get(target, fnName) {
                    if (fnName === 'get') {
                        return funcStack.reduce((val, nextfn) => {
                            return fn(val)
                        }, value)
                    }
                    funcStack.push(window[fnName])
                    return proxy  //返回一个proxy对象,以便链式操作
                }
            })
            return proxy
        }
    }())

    var add = x => x * 2;
    var math = y => y + 10;
    pipe(3).add.math.get // 16

set(target, key, value)方法用于拦截某个属性的赋值操作

target: 目标对象
key: 要设置的key值
value: 设置的value值
返回值: Boolean

假如有一个prosen对象,要设置的值不能小于100,那么就可以使用 set方法拦截。

    const prosen = {
        a: 101,
        b: 46,
        c: 200
    }
    const proxy = new Proxy(prosen, {
        set(target, key, value) {
            if (value < 100) {
                throw Error(`${value}值不能小于100`)
            }
            target[key] = value
        }
    })

上面代码对prosen对象赋值,我们可以拦截判断它赋值如果小于100就给它提示错误。

使用场景

  • 可以实现数据绑定,即数据发生变化时,我们可以拦截到,实时的更新DOM元素。
  • 还可以设置对象的内部数据不可被修改,表示这些属性不能被外部访问和修改,这是可以使用getset, 如下例

规定对象的内部属性以_开头的属性不能进行读写操作。

    const obj = {
        name: 'qiqingfu',
        age: 21,
        _money: -100000,
        _father: 'xxx'
    }
    function isSeal(key) {
        if (key.charAl(0) === '_') {
            return true
        }
        return false
    }
    const proxy = new Proxy(obj, {
        get(target, key) {
            if (isSeal(key)) {
                throw Error(`${key},为内部属性,不可以读取`)
            }
            return target[key]
        },
        set(target, key, value) {
            if (isSeal(key)) {
                throw Error(`${key},为内部属性,不可以修改`)
            }
            target[key] = value
            return true
        }
    })

以上代码obj对象设置了内部属性,以_开头的不支持读写。那么可以使用Proxy对其进行拦截判断。get和set中的key属性如果是以_开头的属性就提示错误。 set方法修改完值后,返回的是一个布尔值。 true成功,反则false为修改失败。


apply(target, context, args) 方法可以拦截函数的调用,call()、apply()

target: 目标对象,
context: 目标对象的上下文对象
args: 函数调用时的参数数组

const proxy = new Proxy(function(){}, {
    apply(target, context, args) {
            console.log(target, 'target')
            console.log(context, 'context')
            console.log(args, 'args')
            }
        })
    const obj = {
        a: 1
    }
    proxy.call(obj,1,2,3)

上面的代码是拦截一个函数的执行,分别打印:
target -> function(){}: 目标对象
context -> {a: 1}: 目标对象的上下文对象,也就是函数的调用者,这里我们使用call,让obj对象来调用这个函数。
args -> [1,2,3]: 目标对象函数调用时我们传递的参数,这里会以数组的形式接受。

例子:
再说下面一个例子之前,先了解一下Reflect.apply(), 下面是 MDN 的解释
Reflect.apply() 通过指定的参数列表发起对目标(target)函数的调用。

语法: Reflect.apply(target, context, args)

target: 目标函数
context: 目标函数执行的上下文
args: 函数调用时传入的实参列表,该列表应该是一个类数组的对象

该方法和ES5的 function.prototype.apply() 方法类似。

下面对 sum 函数的调用进行拦截,并且将函数的执行结果 *2

    const sum = (num1, num2) => {
        return num1 + num2
    }
    const proxy = new Proxy(sum, {
        apply(target, context, args) {
            // 我们可以通过 Reflect.apply()来调用目标函数
            return Reflect.apply(...arguments) * 2
        }
    })

    proxy(3,4)  // 14

以上代码是对 sum函数进行代理,并且将其执行结果 * 2


has(target, key ) 方法即拦截 hasProperty操作, 判断对象是否具有某个属性时,这个方法会生效。

target: 目标对象,
key: 对象的属性
返回值是一个布尔值

如果原对象不可配置或者禁止扩展, 那么has拦截会报错。 for in循环虽然也有 in 操作符,但是has对 for in 循环不生效.

has在什么情况下会进行拦截:

  • 属性查询: 例如 foo in window
  • 继承属性查询: foo in Object.create(proxy)
  • with检查: with(proxy) {}
  • Reflect.has()

例1:
使用 has方法隐藏属性,使其不被 in 操作符发现。 就比如说对象以_开头的属性不能被发现。

    const prosen = {
        name: 'qiqingfu',
        _age: 21
    }
    const proxy = new Proxy(prosen, {
        has(target, key) {
            if (key.chatAt(0) === '_') {
                return false
            }
            return key in target
        }
    })

例2: with检查

with的定义总结

  • 在with语句块中,只是改变了对变量的遍历顺序,由原本的从执行环境开始变为从with语句的对象开始。当尝试在with语句块中修改变量时,会搜索with语句的对象是否有该变量,有就改变对象的值,没有就创建,但是创建的变量依然属于with语句块所在的执行环境,并不属于with对象。

  • 离开with语句块后,遍历顺序就会再次变成从执行环境开始。
  • with语句接收的对象会添加到作用域链的前端并在代码执行完之后移除。

关于js with语句的一些理解

    let a = 'global a'
    const obj = {
        a: 1,
        b: 2
    }
    const fn = key => {
        console.log(key)
    }
    const proxy = new Proxy(obj, {
        has(target, key) {
            console.log(target, 'target')
            console.log(key, 'key')
        }
    })
    with(proxy) {
        fn('a')
    }
    //依此打印
    // {a: 1, b: 2} target
    // fn  key
    // a

以上代码是对obj对象进行代理, 通过with检查, 访问代理对象的 a 属性会被 has方法拦截。那么拦截的第一个target就是目标对象, 而第二个参数key是访问 a时的with语句块所在的执行环境。


construct(target, args) 方法用于拦截 new 命令。

target: 目标函数,
args: 构造函数的参数对象
返回值必须是一个 对象, 否则会报错。

    const proxy = new Proxy(function() {}, {
        construct(target, args) {
            console.log(target, 'target')
            console.log(args, 'args')
            return new target(args)
        }
    })
    new proxy(1,2)

    // function() {}  'target'
    // [1,2]  'args'

如果返回值不是对象会报错


deleteProperty(target, key) 拦截对象的 delete操作

target: 目标对象
key: 删除的哪个key值
返回值: 布尔值, true成功,false失败

目标对象不可配置(configurable)属性不能被deleteProperty删除, 否则会报错

const obj = Object.defineProperties({}, {
    a: {
        value: 1,
        configurable: false,
    },
    b: {
        value: 2,
        configurable: true
    }
})
const proxy = new Proxy(obj, {
    deleteProperty(target, key) {
        delete target[key]
        return true;
    }
    })

delete proxy.a  // 报错
delete proxy.b // true

以上代码拦截 obj对象, 当进行删除不可配置的属性a时,会报错。删除b属性时则成功。

应用场景:
我们可以指定内置属性不可被删除。如以_开头的属性不能被删除

const obj = {
    _a: 'a',
    _b: 'b',
    c:  'c'
}
const proxy = new Proxy(obj, {
    deleteProperty(target, key) {
        if (key.charAt(0) === '_') {
            throw Error(`${key}属性不可被删除`)
            return false
        }
        delete target[key]
        return true
    }
})

defindProperty(target, key, descriptor)方法拦截Object.defindProperty()操作

target: 目标对象,
key: 目标对象的属性
descriptor: 要设置的描述对象
返回值: 布尔值, true添加属性成功, false则会报错

const proxy = new Proxy({}, {
    defineProperty(target, key, descriptor) {
        console.log(target, 'target')
        console.log(key, 'key')
        console.log(descriptor, 'descriptor')
        return true
    }
})
Object.defineProperty(proxy, 'a', {
    value: 1
})

以上代码是拦截一个对象的Object.defindProperty()添加属性的操作, 如果返回值为true,表示添加成功。返回值false则会报错。
以上代码的执行结果:


getPrototypeOf(target) 方法,用来拦截获取对象原型。

target: 代理对象

可以拦截一下获取原型的操作:

  • Object.prototype. __ proto __
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf() 获取一个对象的原型对象
  • instance 操作符

Object.prototype.isPrototypeOf() 方法

检测一个对象的原型链上有没有这个对象

语法: Objectactive.isPrototypeOf(object), 检测object对象的原型链上有没有Objectactive这个对象, 如果有返回true, 否则返回false

    const Objectactive = {a: 1}
    const object = Object.create(Objectactive)
    Objectactive.isPrototypeOf(object) // true

以上代码 Objectactive作为 object的原型对象,然后通过 isPrototypeOf 检测object对象的原型链上有没有Objectactive这个对象。 理所当然返回 true

使用 getPrototypeOf()拦截

const Objectactive = {a: 1}
    const object = Object.create(Objectactive)
    const proxy = new Proxy(object, {
        getPrototypeOf(target) {
            console.log(target, 'target')
            return Object.getPrototypeOf(target)
        }
    })
    let bl = Objectactive.isPrototypeOf(proxy)
    console.log(bl)

    // 依此打印结果:
    /*
        {
            __proto__:
            a: 1,
            __proto__: Object
        } 'target'

        true
    */

以上代码对 object对象进行代理,当访问原型对象时,通过getPrototypeOf()方法拦截,target就是代理对象。

getPrototypeOf()方法的返回值必须是 null 或者对象,否则报错。

isExtensible(target) 方法拦截 Object.isExtensible()方法

Object.isExtensible() 方法返回一个布尔值,其检查一个对象是否可扩展。

target: 目标对象

isExtensible()方法有一个强限制,它的返回值必须与目标对象的 isExtensible属性保持一致。

    const testObj = {
        name: 'apy'
    }
    const proxy = new Proxy(testObj, {
        isExtensible(target) {
            console.log('拦截对象的isExtensible操作')
            return true; // 这里要返回true, 因为目标对象现在是可扩展的,如果返回 false会报错
        }
    })
    console.log(Object.isExtensible(proxy)) 

    // 打印:

    // 拦截对象的isExtensible操作
    // true

以上代码通过Object.isExtensible()检测一个对象是否可扩展,会被配置选项中的 isExtensible方法拦截。

那么什么情况下可以 return false

Object.preventExtensions(object): 将一个对象设置为不可扩展的

const testObj = {
    name: 'apy'
}
Object.preventExtensions(testObj) // 将 testObj对象设置为不可扩展 

const proxy = new Proxy(testObj, {
    isExtensible(target) {
        console.log('拦截对象的isExtensible操作')
        return false // 因为testObj对象不可扩展,返回值要和目标对象的 Object.isExtensible一致。
    }
})

Object.isExtensible(testObj)

以上代码通过 proxy拦截对象的 Object.isExtensible方法, 并且拦截的返回值与Object.isExtensible一致。否则报错


ownKeys(target)方法用于拦截对象自身的属性读取操作

target: 目标对象
返回值: Array<String, Symbol>, 返回值为数组,且数组中只能包含字符串或Symbol类型的

会被 ownKeys 拦截的读取操作

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
使用Object.keys 方法时,有三类属性会被 ownKeys 过滤掉,并不会返回.
  • 目标对象 target 上压根不存在的属性
  • 属性名为 Symbol
  • 还有就是目标对象上不可遍历的属性
    const obj = {
        a: 1,
        b: 2,
        [Symbol.for('c')]: 3
    }
    Object.defineProperty(obj, 'd', {
        value: 4,
        enumerable: false
    })
    const proxy = new Proxy(obj, {
        ownKeys(target) {
            return ['a', 'b', [Symbol('c')], 'd', 'e']
        }
    })
    Object.keys(obj).forEach(key => {
        console.log(key)
    })
    // a
    // b

以上代码定义了一个 obj对象, 有其属性a, b, [Symbol],d。并且d属性是不可扩展的。那么 ownKeys方法显式返回 不可遍历的属性(d)Symbol和不存在的属性e都会被过滤掉,那么最终返回a和b

注意:
  • 如果目标对象包含不可配置(configurable)的属性,那么该属性必须被 ownkeys方法返回。
  • 如果目标对象是不可扩展(preventExtensions)的对象,那么 ownkeys返回必须返回这个对象的原有属性,不能包含额外的属性。

setPrototypeOf(target, proto) 方法拦截 Object.setPrototypeOf方法

target: 目标对象
proto: 要设置的原型对象
返回值 布尔值

设置一个对象的原型对象操作,会被 setPrototypeOf拦截。

    const obj = {}
    const proxy = new Proxy(obj, {
        setPrototypeOf(target, proto) {
            console.log('拦截设置原型操作')
            // 内部手动设置原型,并且返回 boolean
            return Object.setPrototype(target, proto)
        }
    })
    Object.setPrototypeOf(proxy, {a: 1})

以上代码拦截Object.setPrototypeOf方法,所以会打印 拦截设置原型操作

使用场景, 禁止修改一个对象的原型,否则报错

如上例子,拦截一个修改对象原型的操作,抛出相应的错误就可以。

    const foo = {}
    const proxy = new Proxy(foo, {
        setPrototypeOf(target, key) {
            throw Error(`${target}不可以修改原型对象`)
        }
    })
    Object.setPrototypeOf(proxy, {a: 1})  // 报错

ES6 Proxy拦截器详解的更多相关文章

  1. Struts2 之 modelDriven & prepare 拦截器详解

    struts2 ModelDriven & Prepareable 拦截器 前面对于 Struts2 的开发环境的搭建.配置文件以及 Struts2 的值栈都已经进行过叙述了!这次博文我们讲解 ...

  2. [转]SpringMVC拦截器详解[附带源码分析]

      目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

  3. SpringMVC拦截器详解[附带源码分析]

    目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:h ...

  4. struts2 18拦截器详解(七)

    ChainingInterceptor 该拦截器处于defaultStack第六的位置,其主要功能是复制值栈(ValueStack)中的所有对象的所有属性到当前正在执行的Action中,如果说Valu ...

  5. spring--处理器拦截器详解——跟着开涛学SpringMVC

    5.1.处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器) 类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. ...

  6. AspectCore动态代理中的拦截器详解(一)

    前言 在上一篇文章使用AspectCore动态代理中,简单说明了AspectCore.DynamicProxy的使用方式,由于介绍的比较浅显,也有不少同学留言询问拦截器的配置,那么在这篇文章中,我们来 ...

  7. SpringMVC拦截器详解

    拦截器是每个Web框架必备的功能,也是个老生常谈的主题了. 本文将分析SpringMVC的拦截器功能是如何设计的,让读者了解该功能设计的原理. 重要接口及类介绍 1. HandlerExecution ...

  8. struts2 18拦截器详解(十)

    ModelDrivenInterceptor 该拦截器处于defaultStack中的第九的位置,在ScopedModelDrivenInterceptor拦截器之后,要使该拦截器有效的话,Actio ...

  9. Struts2拦截器详解

    一.Struts2拦截器原理: Struts2拦截器的实现原理相对简单,当请求struts2的action时,Struts 2会查找配置文件,并根据其配置实例化相对的    拦截器对象,然后串成一个列 ...

随机推荐

  1. 1093 Count PAT's(25 分)

    The string APPAPT contains two PAT's as substrings. The first one is formed by the 2nd, the 4th, and ...

  2. java防止表单重复提交的几种方法

    转载大神 1. 使用session同步和token机制来防止并发重复提交 https://blog.csdn.net/hejingyuan6/article/details/50487777 2. S ...

  3. PHP根据经纬度计算距离

    思路: 公式: W为纬度对应的弧度,J为经度对应的弧度,如上图所示 下面代码  lat是纬度  lng是经度 /** * 根据经纬度算距离,返回结果单位是公里,先纬度,后经度 * @param $la ...

  4. 用CSS控制图片大小显示的方法

    图片自动适应大小是一个非常常用的功能,在进行制作的时候为了防止图片撑开容器而对图片的尺寸进行必要的控制,我们可不可以用CSS控制图片使它自适应大小呢? 可以通过按比例缩小或者放大到某尺寸(自己指定), ...

  5. docker exit status 255解决

    一 windows开发整docker就是痛苦,在公司win7电脑想拿起几年没再用的docker 结果直接报错 Error getting IP address: ssh command error: ...

  6. SQL语句中的having和where的区别

    --首先,两个都是用来进行筛选的: --区别在于 1.当分组筛选的时候使用having eg: 在emp中,查出最低工资小于1000的部门号 select deptno from emp group ...

  7. webpack源码之ast简介

    什么是AST 树是一种重要的数据结构,由根结点和若干颗子树构成的. 根据结构的不同又可以划分为二叉树,trie树,红黑树等等.今天研究的对象是AST,抽象语法树,它以树状的形式表现编程语言的语法结构, ...

  8. jsp连接sqlite、Sqlite相对路径绝对路径问题(转)

    转自  http://blog.csdn.net/sxy12138/article/details/52304884 假如在java中, # 数据库连接jdbc.jdbc-url=jdbc:sqlit ...

  9. git 如何生成 SSH 公钥

    1.打开你的git bash 窗口 2.进入.ssh目录:cd ~/.ssh 3.找到id_rsa.pub文件:ls 4.查看公钥:cat id_rsa.pub    或者vim id_rsa.pub ...

  10. AGC015 C Nuske vs Phantom Thnook(前缀和)

    题意 题目链接 给出一张$n \times m$的网格,其中$1$为蓝点,$2$为白点. $Q$次询问,每次询问一个子矩阵内蓝点形成的联通块的数量 保证任意联通块内的任意蓝点之间均只有一条路径可达 S ...