代理和反射的应用

1. 代理和反射

代理是什么?

通过调用 new Proxy() ,你可以创建一个代理用来替代另一个对象(被称之为目目标对象) ,这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。代理允许你拦截目标对象上的底层操作,而这本来是JS引擎的内部能力,拦截行为适用了一个能响应特定操作的函数(被称之为陷阱);

反射是什么?

被Reflect对象所代表的反射接口,是给底层操作提供默认行为的方法的集合,这些操作是能够被代理重写的。每个代理陷阱都有一个对应的反射方法,每个方法都与对应的陷阱函数同名,并且接收的参数也与之一致。

创建一个简单的代理

使用Proxy构建可以创建一个简单的代理对象,需要传递两个参数:目标对象以及一个处理器,后者是定义一个或多个陷阱函数的对象。如果不定义陷阱函数,则依然使用目标对象的默认行为。

常用陷阱函数

1 基本陷阱函数

(1).使用Set陷阱函数验证属性值

假如有这样一个场景,必须要求对象的属性值必须只能是数值,这就意味着该对象每个新增属性时都要被验证,并且在属性不为数值属性时就应该抛出错误。因此就需要使用set陷阱函数来重写set函数的默认行为,set陷阱函数接收四个参数:

  1. trapTarget:代理的目标对象;
  2. key:需要写入的属性的键;
  3. value:被写入属性的值;
  4. receiver:操作发生的对象(通常是代理对象)

Reflect.set()是set陷阱函数对应的反射方法,同时也是set操作的默认行为,Reflect.set()方法与set陷阱函数一样,能够接受四个参数。

示例代码:

//set陷阱函数

let target = {

    name:‘target‘

}

let proxy = new Proxy(target,{

    set(tarpTarget,key,value,receiver){

        if(!tarpTarget.hasOwnProperty(key)){

            if(isNaN(value)){

                throw new Error(‘property must be number‘);

            }

        }

        return Reflect.set(tarpTarget,key,value,receiver);

    }

});

proxy.msg=‘hello proxy‘; //Uncaught Error: property must be number

通过set陷阱函数就可以检测设置属性时属性值的类型,当属性值不是数字时,就会抛出错误。

(2).使用get陷阱函数进行对象外形验证

对象外形(Object Shape)指的是对象已有的属性与方法的集合。能够使用代理很方便进行对象外形验证。由于使用属性验证只需要在读取属性时被触发,因此只需要使用get陷阱函数。该函数接受三个参数:

  1. trapTarget:代理的目标对象;
  2. key:需要读取的属性的键;
  3. receiver:操作发生的对象(通常是代理对象);

相应的Reflect.get()方法同样拥有这三个参数。进行对象外形验证的示例代码:

//get陷阱函数

let target={

    name:‘hello world‘

}

let proxy = new Proxy(target,{

        get(tarpTarget,key,receiver){

            if(!(key in tarpTarget)){

                throw new Error(‘不存在该对象‘);

            }

            return Reflect.get(tarpTarget,key,receiver);

        }

    });

console.log(proxy.name); //hello world

console.log(proxy.age); // Uncaught Error: 不存在该对象

使用get陷阱函数进行对象外形验证,由于target对象存在name属性,所以可以正常返回,当获取age属性时,由于该属性并不存在,所以会抛出错误。

(3).使用has陷阱函数隐藏属性

in运算符用于判断指定对象中是否存在某个属性,如果对象的属性名与指定的字符串或符号值相匹配,那么in运算符就会返回true。无论该属性是对象自身的属性还是其原型的属性。

has陷阱函数会在使用in运算符的情况下被调用,控制in运算符返回不同的结果,has陷阱函数会传入两个参数:

  1. trapTarget:代理的目标对象;
  2. key:属性键;

Reflect.has()方法接收相同的参数,并向in运算符返回默认的响应结果,用于返回默认响应结果。

例如想要隐藏value属性:

//has陷阱函数

let target = {

    value:‘hello world‘

}

let proxy = new Proxy(target,{

    has(tarpTarget,key){

        if(Object.is(key,‘value‘)){

            return false;

        }

        Reflect.has(tarpTarget,key);

    }

})

console.log(‘value‘ in proxy); //false

使用has陷阱函数,能够控制in运算符的结果,value属性在target对象中存在,通过代理的has陷阱函数使得在检查value属性时返回false,达到隐藏属性的效果。

(4).使用deleteProperty陷阱函数避免属性被删除

deleteProperty 陷阱函数会在使用delete 运算符删除对象属性时被调用,该方法接收两个参数:

  1. trapTarget:代理的目标对象;
  2. key:需要删除的键;

Reflect.deleteProperty() 方法也接受这两个参数,并提供了 deleteProperty 陷阱函数的默认实现。你可以结合 Reflect.deleteProperty()方法以及 deleteProperty 陷阱函数,来修改 delete 运算符的行为。例如,能确保 value 属性不被删除:

let target = {

    name: "target",

    value: 42

};

let proxy = new Proxy(target, {

    deleteProperty(trapTarget, key) {

        if (key === "value") {

            return false;

        } else {

            return Reflect.deleteProperty(trapTarget, key);

        }

    }

});

// 尝试删除 proxy.value

console.log("value" in proxy); // true

let result1 = delete proxy.value;

console.log(result1); // false

2. 原型代理上的陷阱函数

在调用Object.setPrototypeOf()和getPrototypeOf()方法时,可以使用setPrototypeOf和getPrototypeOf陷阱函数来影响Object上相应的两个方法的效果。setPrototypeOf陷阱函数接收两个参数:

  1. trapTarget:代理的目标对象;
  2. proto:需要被用作原型的对象;

setPrototypeOf()方法与Reflect.setPrototypeOf()传入相同的参数。另外,getPrototypeOf陷阱函数只接收trapTarget参数,Reflect.getPrototype也只接收一个参数。

例如,通过返回 null 隐藏了代理对象的原型,并且使得该原型不可被修改:

//原型代理上的陷阱函数

let target = {};

let proxy = new Proxy(target, {

    getPrototypeOf(trapTarget) {

        return null;

    },

    setPrototypeOf(trapTarget, proto) {

        return false;

    }

});

let targetProto = Object.getPrototypeOf(target);

let proxyProto = Object.getPrototypeOf(proxy);

console.log(targetProto === Object.prototype); // true

console.log(proxyProto === Object.prototype); // false

console.log(proxyProto); // null

// 成功

Object.setPrototypeOf(target, {});

// 抛出错误

Object.setPrototypeOf(proxy, {});
  使用 target 对象作为参数调用Object.getPrototypeOf() 会返回一个对象值;而使用 proxy 对象调用该方法则会返回null ,因为 getPrototypeOf 陷阱函数被调用了。类似的,使用 target 去调用Object.setPrototypeOf() 会成功;而由于 setPrototypeOf 陷阱函数的存在,使用 proxy则会引发错误。

3 对象可扩展性的陷阱函数

ES5 通过Object.preventExtensions() 与 Object.isExtensible() 方法给对象增加了可扩展性。而 ES6 则通过 preventExtensions 与 isExtensible 陷阱函数允许代理拦截对于底层对象的方法调用。这两个陷阱函数都接受名为 trapTarget 的单个参数,此参数代表代理的目标对象。 isExtensible 陷阱函数必须返回一个布尔值用于表明目标对象是否可被扩展,而 preventExtensions 陷阱函数也需要返回一个布尔值,用于表明操作是否已成功。同时也存在Reflect.preventExtensions() 与Reflect.isExtensible() 方法,用于实现默认的行为。这两个方法都返回布尔值,因此它们可以在对应的陷阱函数内直接使用。

let target = {};

let proxy = new Proxy(target, {

    isExtensible(trapTarget) {

    return Reflect.isExtensible(trapTarget);

    },

    preventExtensions(trapTarget) {

    return false

    }

});

console.log(Object.isExtensible(target)); // true

console.log(Object.isExtensible(proxy)); // true

Object.preventExtensions(proxy);

console.log(Object.isExtensible(target)); // true

console.log(Object.isExtensible(proxy)); // true

4 属性描述符的陷阱函数

ES5 最重要的特征之一就是引入了Object.defineProperty()方法用于定义属性的特性。在JS 之前的版本中,没有方法可以定义一个访问器属性,也不能让属性变成只读或是不可枚举。而这些特性都能够利用 Object.defineProperty()方法来实现,并且你还可以利用Object.getOwnPropertyDescriptor()方法来检索这些特性。代理允许你使用 defineProperty 与 getOwnPropertyDescriptor 陷阱函数,来分别拦截对于Object.defineProperty() 与 Object.getOwnPropertyDescriptor() 的调用。 defineProperty
陷阱函数接受下列三个参数:

  1. trapTarget :需要被定义属性的对象(即代理的目标对象) ;
  2. key :属性的键(字符串类型或符号类型) ;
  3. descriptor :为该属性准备的描述符对象。

5 ownKeys陷阱函数

ownKeys 代理陷阱拦截了内部方法 [[OwnPropertyKeys]] ,并允许你返回一个数组用于重写该行为。返回的这个数组会被用于四个方法:
Object.keys() 方法、Object.getOwnPropertyNames()
方法、Object.getOwnPropertySymbols()方法与Object.assign() 方法,其中 Object.assign() 方法会使用该数组来决定哪些属性会被复制。

ownKeys 陷阱函数接受单个参数,即目标对象,同时必须返回一个数组或者一个类数组对象。你可以使用 ownKeys 陷阱函数去过滤特定的属性,以避免这些属性被Object.keys() 方法、Object.getOwnPropertyNames() 方法、Object.getOwnPropertySymbols()
方法或 Object.assign() 方法使用。

6 apply与construct陷阱函数

只有 apply 与 construct 要求代理目标对象必须是一个函数。函数拥有两个内部方法:[[Call]] 与 [[Construct]] ,前者会在函数被直接调用时执行,而后者会在函数被使用
new 运算符调用时执行。 apply 与 construct陷阱函数对应着这两个内部方法,并允许你对其进行重写。apply 陷阱函数会接收到下列三个参数(
Reflect.apply() 也会接收这些参数) :

  1. trapTarget :被执行的函数(即代理的目标对象) ;
  2. thisArg :调用过程中函数内部的 this 值;
  3. argumentsList
    :被传递给函数的参数数组。

当使用 new 去执行函数时, construct 陷阱函数会被调用并接收到下列两个参数:

  1. trapTarget :被执行的函数(即代理的目标对象) ;
  2. argumentsList
    :被传递给函数的参数数组。

Reflect.construct()方法同样会接收到这两个参数,还会收到可选的第三参数 newTarget,如果提供了此参数,则它就指定了函数内部的 new.target 值。

使用apply和construct陷阱函数有这样一些应用场景:

验证函数的参数

假如需要保证所有参数都是某个特定类型,可使用
apply 陷阱函数进行验证:

//apply和construct陷阱函数

let sum = function (arr=[]) {

    return arr.reduce((previous,current)=>previous+current);

}

let proxy = new Proxy(sum,{

    apply(trapTarget,thisArg,argumentList){

        argumentList[0].forEach((item)=>{

            if(typeof item != ‘number‘){

                throw new Error(‘不是数字类型‘);

            }

        })

        return Reflect.apply(trapTarget,thisArg,argumentList);

    },

    construct(trapTarget,argumentList){

        throw new Error(‘不能使用new‘);

    }

});

console.log(proxy([1,2,3,4])); // 10

console.log(proxy([1, "2", 3, 4]));//Uncaught Error: 不是数字类型Uncaught Error: 不是数字类型

let result = new proxy(); //Uncaught Error: 不能使用new

 可被撤销的代理

在被创建之后,代理通常就不能再从目标对象上被解绑。有的情况下你可能想撤销一个代理以便让它不能再被使用。当你想通过公共接口向外提供一个安全的对象,并且要求要随时都能切断对某些功能的访问,这种情况下可被撤销的代理就会非常有用。
你可以使用Proxy.revocable()方法来创建一个可被撤销的代理,该方法接受的参数与Proxy 构造器的相同:一个目标对象、一个代理处理器,而返回值是包含下列属性的一个对象:

  1. proxy :可被撤销的代理对象;
  2. revoke :用于撤销代理的函数;

当 revoke() 函数被调用后,就不能再对该 proxy 对象进行更多操作。例如:

let target = {

    name: "target"

};

let { proxy, revoke } = Proxy.revocable(target, {});

console.log(proxy.name); // "target"

revoke();

// 抛出错误

console.log(proxy.name);

总结

  1. 在 ES6 之前,特定对象(例如数组)
    会显示出一些非常规的、无法被开发者复制的行为,而代理的出现改变了这种情况。代理允许你为一些 JS 底层操作自行定义非常规行为,因此你就可以通过代理陷阱来复制 JS 内置对象的所有行为。在各种不同操作发生时(例如对于 in运算符的使用)
    ,这些代理陷阱会在后台被调用。
  2. 反射接口也是在 ES6
    中引入的,允许开发者为每个代理陷阱实现默认的行为。每个代理陷阱在 Reflect 对象(ES6 的另一个新特性) 上都有一个同名的对应方法。将代理陷阱与反射接口方法结合使用,就可以在特定条件下让一些操作有不同的表现,有别于默认的内置行为。
  3. 可被撤销的代理是一种特殊的代理,可以使用 revoke() 函数去有效禁用。 revoke() 函数终结了代理的所有功能,因此在它被调用之后,所有与代理属性交互的意图都会导致抛出错误。
  4. 尽管直接使用代理是最有力的使用方式,但你也可以把代理用作另一个对象的原型。但只有很少的代理陷阱能在作为原型的代理上被有效使用,包括
    get 、 set 与 has 这几个,这让这方面的用例变得十分有限

原文地址:https://www.cnblogs.com/hjy-21/p/12381152.html

时间: 2024-10-11 03:36:18

代理和反射的应用的相关文章

如何理解代理和反射?

一.本节主要点 代理的概念 静态代理和动态代理 cglib 什么是代理呢? 代理,就是一个角色代表另一个角色去完成某件事. 比如,你肚子饿了,又懒得出去吃饭,这时候,你的朋友正好要出去,你让他帮忙打包.那他就是代理类.你就是委托他去做事. 如何理解代理和反射?

5.java动态代理、反射

1.java动态代理.反射(IDEA导入JUnit4) 1.1.反射 原文地址:https://www.cnblogs.com/yaboya/p/9157036.html

Java中的动态代理及反射机制

面向对象的基本原则封装.继承.多态,在java中多态机制,表现为变量多态,方法多态,这都是指的是因对象所属的类不同,而调用不同的类方法:对于对象的方法,还有函数重载,java中的函数的签名是由函数名+参数方法来定的,不能仅由返回值不同来定. 反射Reflect 运行时获取类的类型,域,方法等各种属性. Class是一个类,其实例对应其他不同分 (CalculatorImpl)Class.forName("CalculatorImpl").newInstance(); newInstan

动态代理机制+反射

一个接口,一个类,是实现动态代理的核心!!! InvocationHandler接口 通过创建InvocationHandler接口创建自己的调用处理器. invoke方法(可以调用被代理对象方法,也可以调用代理方法) Proxy 类 (newProxyInstance方法就是创建一个代理类对象) loader:类加载器 interfaces:接口 h:调用对象 注解: 注解的定义:public @interface 注解名 元注解(定义注解的注解): [email protected]: 表示

代理、反射、注解、hook

代理 通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能. 代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用 这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法. 动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强.所有使用装饰者模式的案例都可以使用动态代理来替换. /** * subject(抽象主题角色): * 真实主题与代理主题的共同接口. */ interf

代理(Proxy)和反射(Reflection)

前面的话 ES5和ES6致力于为开发者提供JS已有却不可调用的功能.例如在ES5出现以前,JS环境中的对象包含许多不可枚举和不可写的属性,但开发者不能定义自己的不可枚举或不可写属性,于是ES5引入了Object.defineProperty()方法来支持开发者去做JS引擎早就可以实现的事情.ES6添加了一些内建对象,赋予开发者更多访问JS引擎的能力.代理(Proxy)是一种可以拦截并改变底层JS引擎操作的包装器,在新语言中通过它暴露内部运作的对象,从而让开发者可以创建内建的对象.本文将详细介绍代

代理(Proxy)和反射(Reflection) (转)

转自:http://www.cnblogs.com/xiaohuochai/p/7268600.html 前面的话 ES5和ES6致力于为开发者提供JS已有却不可调用的功能.例如在ES5出现以前,JS环境中的对象包含许多不可枚举和不可写的属性,但开发者不能定义自己的不可枚举或不可写属性,于是ES5引入了Object.defineProperty()方法来支持开发者去做JS引擎早就可以实现的事情.ES6添加了一些内建对象,赋予开发者更多访问JS引擎的能力.代理(Proxy)是一种可以拦截并改变底层

插件化知识详细分解及原理 之代理,hook,反射

上一篇我们说了Binder机制,通过aidl的demo和系统源码的对比进行了运行过程的分析,这一篇我们说代理模式及反射,之前说过了,只是为了梳理插件化需要了解的知识点,所以不会特别深的去讲解. 代理模式: 也叫做委托模式,分为静态代理和动态代理.代理模式也是平时比较常用的设计模式之一,代理模式有代码简洁,高扩展性的特性.主要目的就是为访问者提供一个代理,以达到限制某个对象的访问,也就是说想访问一个对象,其实我给你的是一个代理,不让你直接使用我.估计不理解的人会问为什么使用代理模式,他限制了对象的

Java反射学习总结四(动态代理使用实例和内部原理解析)

通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类.解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成. 动态代理知识点: Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类: 1.InvocationHandler接口: