码迷,mamicode.com
首页 > 其他好文 > 详细

ES6 Proxy拦截器详解

时间:2018-11-18 19:29:19      阅读:175      评论:0      收藏:0      [点我收藏+]

标签:搜索   并且   next   doc   实时   href   mat   例子   isp   

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)

ES6 Proxy拦截器详解

标签:搜索   并且   next   doc   实时   href   mat   例子   isp   

原文地址:https://www.cnblogs.com/qiqingfu/p/9978659.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!