JavaScript学习笔记(十五):Proxy代理器

一、什么是Proxy代理器

ES6新增的代理提供了拦截并向基本操作嵌入额外行为的能力,具体意思是可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

Proxy可以理解成, 在目标对象之前设置一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

初识代理器

可以通过提供的Proxy构造函数,用来生成Proxy实例

{
    // 第一个参数为代理的对象,第二个参数也是一个对象,对拦截的处理
    let obj = new Proxy({}, {
        get: function (target, propKey, receiver) {
            console.log(`getting${propKey}`);
            // console.log(target);
            return Reflect.get(target, propKey, receiver);
        },
        set: function (target, propKey, value, receiver) {
            console.log(`setting${propKey}`);
            // console.log(value);
            return Reflect.set(target, propKey, value, receiver)
        }
    })
    obj.count = 1;
    ++obj.count;
    console.log(obj);
}

在这里插入图片描述

创建Proxy语法

  • new Proxy(target,handler)

Proxy对象的所有用法,都是这种形式,不同的知识handler参数的写法

  • new Proxy()表示生成一个Proxy实例
  • target参数表示所要拦截的目标对象
  • handler参数也是一个对象,用来定制拦截行为

拦截读取属性行为

要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对代理的对象(目标对象)进行操作

{
	// 拦截读取属性行为
    let proxy = new Proxy({},{
        get(target,propKey){
            return 35
        }
    })
    console.log(proxy.name);
    console.log(proxy.time);
    console.log(proxy.age); // 都为35
}

如果没有对handler设置任何拦截,那就等同于直接通向目标对象

{
    // 如果没有设置拦截器,那就直接通向原对象
    let target = {};
    let handler = {};
    let p = new Proxy(target,handler)
    p.a = 'b';
    console.log(target,p.a); // { a: 'b' } b
    target.a = 'bbb';
    console.log(target,p.a); // { a: 'bbb' } bbb
}

Proxy代理器也可以设置到对象的属性上,这样可以再object对象上调用

{
	// 可以将Proxy对象设置到object.proxy属性,从而可以再object对象上调用
    // 需要时直接调用
    {
        let obj = {
            proxy:new Proxy(this,{
                get(target,prop){
                    console.log(`getting:${prop}`);
                    return target[prop] ?? 'Not Found'
                }
            }),
        }
        let x = obj.proxy.a; // 通过代理,getting:a
        console.log(x); // Not Found
    }
}

同一个拦截器对象,也可以设置拦截多个操作

{
    // 同一个拦截器对象,可以设置拦截多个操作
    let handler = {
        // 设置属性时拦截
        get(target,name){
            if(name == 'prototype'){
                return Object.prototype
            }
            return `Hello,${name}`
        },
        // 函数调用时拦截
        apply(target,thisBinding,args){
            return args[0]
        },
        // 创建构造函数时拦截
        construct(target,args){
            let obj = new target(...args);
            obj.name = 'foo';
            obj.value = args[1];
            return obj;
        }
    }
    let fproxy = new Proxy(function(x,y){return x + y},handler)
    console.log(fproxy(1,2)); // 1
    console.log(new fproxy(1,2)); // { name: 'foo', value: 2 }
    console.log(fproxy.prototype === Object.prototype); //true
    console.log(fproxy.age); // Hello,age
}

Proxy支持的拦截操作一共有13种

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如- - proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。

常用的只有get()和set()方法

二、Proxy实例方法

get()

用于拦截某个属性的读取操作,接受三个参数,依次为目标对象、属性名和proxy实例本身,其中最后一个为可选参数

使用get拦截,实现数组读取负数的索引

{
    // 使用代理拦截实现数组的负索引
    // 第三个参数receiver就是这个代理器的实例
    function createArray(...elements){
        let handler = {
            get(target,propKey,receiver){
                let index = Number(propKey);
                if(index < 0){
                    propKey = String(target.length + index);
                }
                return target[propKey]
            }
        }
        let target = [];
        target.push(...elements);
        return new Proxy(target,handler)
    }
    let arr = createArray('a','b','c','d','e');
    console.log(arr[-2]); // d
}

如果一个属性不可配置且不可写则Proxy不能修改该属性,否则通过Proxy对象访问属性会报错

{
    const target = Object.defineProperties({}, {
        foo: {
            value: 123,
            writable: false,
            configurable: false,
        },
    });
    const handler = {
        get(target, propKey) {
            return "abc";
        },
    };
    const oneProxy = new Proxy(target, handler);
    console.log(oneProxy.foo);
}

在这里插入图片描述
foo属性是只读的并且不可配置的,因此不能代理该属性。

set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实例本身,最后一个也是可选的参数,返回一个布尔值

假设Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的试训过值符合要求

{
    let validator = {
        set(obj,prop,value){
            if(prop === 'age'){
                if(!Number.isInteger(value)){
                    throw new TypeError('the age must be an integer')
                }
                if(value > 200){
                    throw new RangeError('the age seems invalid')
                }
            }
            obj[prop] = value
            return true
        }
    }
    let person = new Proxy({},validator);
	// 可以使用try catch来捕获异常
    try {
        person.age = 250;
        console.log(person.age);
    } catch (error) {
        console.log(error); // 如果大于200会报错
    }
    console.log('end');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值