初识ES6 Proxy属性

3,412 阅读6分钟

前言

Proxy对象是ES6(ECMAScript 6)中提供用来操作对象的一个API,其含义官方给出的解释是:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

  • 小白:额~,读了三遍,还是不太明白。
  • 隔壁老王:Proxy本意是代理,那既然代理,肯定要具备代理的俩基本素质,1.帮谁代理,2.干点什么。所以, 帮谁代理? 帮目标对象代理,干点什么?架设拦截,对外界的访问进行过滤和改写,串起来说就是:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。这下明白了吧,嘿嘿。
  • 小白:喔育,不愧是隔壁老王,就是厉害~

语法

let proxy = new Proxy(target, handler);

tgarget:要代理的目标对象。(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler:定义拦截行为的配置对象(也是一个对象,其内部的属性均为执行操作的函数)。

  • proxy支持的拦截操作一共 13 种,即支持13种代理行为(函数),这些函数如果你需要复写其行为方式,均要定义在handler这个对象中使用,下面我们就针对这些支持的比较常用的操作函数进行举例说明。其余的不是很常用,如果想了解可以参考文末的资料去学习。

get(target, key, receiver)

含义:拦截对象属性的读取
target:要代理的目标对象。
key:属性名。
receiver:proxy实例(可选参数,一般不用)

const proxy1 = new Proxy({
            a: 1
        }, {
            get(target, key) {
              if(Reflect.has(target,key)) {
                return Reflect.get(target,key);
              }else {
                  return false;
              }
              
            }
    })
        proxy1.a //1
        proxy1.b //false

以上,a属性因为有定义,所以可获取到其值为1,b属性没有被定义,输出为false,如果这里没有定义get方法,那么获取操作就会被转发到目标对象身上,默认如果没有此属性,会返回undefined,而这里却返回false,正是因为重新定义了get方法,对返回内容进行了修改,正如其含义,对代理对象属性的读取进行了拦截。

set(target, key, value, receiver)

含义:拦截对象属性的设置
target:要代理的目标对象。
key:要设置的属性名。
value:要设置的属性值。
receiver:proxy实例(可选参数,一般不用)

    const handler = {
           set(target,key,value) {
               if(!Reflect.has(target,key)) {
                   Reflect.set(target,key,value);
               }else {
                  console.log(`${key}属性已存在`) //a属性已存在
               }
           },
           get(target,key) {
            return Reflect.get(target,key);
           }

       }
    const proxy = new Proxy({},handler);
     proxy.a = 1;
     console.log(proxy.a);//1
     proxy.a = 2;
     console.log(proxy.a) //1

以上,设置属性的时候如果当前属性已存在,则不能设置成功,因此,我们可以利用set方法来拦截设置符合我们期望的属性,根据应用场景,自由发挥。

   var obj = {};
   const target = Object.defineProperty(obj, 'a', {
            value: 123,
            writable: false,
    });
    const handler = {
        set(target,key,value) {
            Reflect.set(target,key,value);
        },
        get(target, key) {
            return Reflect.get(target,key);
        }
    };

    const proxy = new Proxy(target, handler);
    proxy.a = 456;
   console.log( proxy.a) //123

以上,当目标对象的某个属性为不可写状态,那么set方法将会失效。

  • 看到很多资料都写到是当目标对象的属性为不可写且不可配置状态,set方法将会失效,但自测发现其结果与configurable属性的状态并无关系,只与writable属性有关,有点小疑惑,希望看到的大佬能帮小弟解惑。

has(target, Key)

含义:判断对象是否具有某个属性。
target:要代理的目标对象。
key:要设置的属性名。
当判断一个对象中是否具有某个属性时,has方法就会生效,典型的操作就是in运算符应用,返回值为布尔类型

     const handler = {
            has(target, key) {
                if (key[0] === '_') {
                    console.log('it is a private property')
                    return false;
                }
                return key in target;
            }
        };
        const target = {
            _a: 123,
            a: 456
        };
        const proxy = new Proxy(target, handler);
        console.log('_a' in proxy) // it is a private property  false
        console.log('a' in proxy);//true

以上,当对proxy使用in运算符时,就会自动触发has方法,如果判断当前属性以_开头的话,就进行拦截,从而不被后面的in运算符发现,实现隐藏某些特定属性的目的。

不过需要注意,当目标对象是不可扩展或者对象的属性是不可配置时has方法不能隐藏目标对象的当前属性,否则拦截会报错。

    var obj = {
            a: 10
        };
       Object.preventExtensions(obj); //使obj对象不可扩展,也就是使其不能增加新的属性
        var p = new Proxy(obj, {
            has: function (target, key) {
                return false;
            }
        });

        'a' in p // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
        
        

   let obj = {};
        Object.defineProperty(obj, "a", {
            configurable: false, //当前属性不可配置
            value: 10,
        });
        var p = new Proxy(obj, {
            has: function (target, key) {
                return false;
            }
        });

        'a' in p // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' which exists in the proxy target as non-configurable
    

apply(target, thisArgs, args)

含义:拦截函数的调用、call和apply操作
target:目标对象。
thisArgs:目标对象的上下文对象(this)。
args:目标对象的参数数组。

    const handler = {
            apply(target, ctx, args) {
                return Reflect.apply(...arguments) * 2;
                //或者
                return target(...args) * 2
            }
        };

        function sum(...args) {
            let num = 0;
            args.forEach((item) => {
                num += item;
            })
            return num;
        }

        var proxy = new Proxy(sum, handler);
        proxy(1, 2) // 6
        proxy.call(null, 5, 6) // 22
        proxy.apply(null, [7, 8]) // 30

以上,目标对象这里是sum,必须是一个函数,否则调用会报错。每当proxy函数被直接调用或者call,apply方式调用都会立即触发apply方法,从而该调用被apply方法拦截,这样就可以利用拦截修改返回值,如果不写apply方法,默认调用sum方法,返回 结果。

construct(target, args,newTarget)

含义:拦截new命令
target:目标对象。
args:构造函数的参数列表。
newTarget:创建实例对象时,new命令作用的构造函数(下面例子的p)。

    var p = new Proxy(function () {}, {
            construct: function (target, args, newTarget) {
                console.log('called: ' + args.join(', '));
                return {
                    value: args[0] * 10
                };

               // return 1 //Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
            }
        });

        (new p(1)).value
        // "called: 1"
        // 10

以上,代理的目标对象必须是一个构造函数(只有构造函数才可以使用new操作符),当执行new p(1)时,会立刻触发construct函数,也就是会被该函数拦截,这里的返回值有讲究,必须返回对象,否则会报错。construct方法执行完毕,p实例也就初始化完成了。

Proxy代理时 target 内部 this 的指向会发生改变

 const target = {
            foo: function () {
                console.log(this === proxy);
            }
        };
        const handler = {};
        const proxy = new Proxy(target, handler);

        target.foo() // false
        proxy.foo() // true

可以看出,当目标对象一旦被proxy代理后,其内部this就会指向proxy,而非自身,因此需要注意这点。

结语

好啦,今天关于Proxy的介绍就到这了,以上也只是针对几个比较常用的操作函数进行了举例说明,如果想了解更多,建议参考以下资料,若有错误,还望各位大神指正!

阮一峰:ECMAScript 6 入门-Proxy
阮一峰:ECMAScript 6 入门-Reflect
Proxy - JavaScript | MDN