Proxy详解

Posted lyralee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Proxy详解相关的知识,希望对你有一定的参考价值。

一.Proxy基础

1. 什么是Proxy?

Proxy是一个构造函数,可以通过它生成一个Proxy实例。

const proxy = new Proxy(target, handler);
// target: 目标对象,待操作对象(可以是普通对象,数组,函数,proxy实例对象)
// handler: 一个对象,最多可包含13个操作方法,也可以是空对象

2. Proxy的作用

1. Proxy是一个ES6语法,它的作用主要是通过handler对象中的拦截方法拦截目标对象target的

某些操作(如读取,赋值,函数调用,new等),然后过滤或者改写这些操作的默认行为,

或者只是在完成这些操作的默认行为的基础上,增加一些副作用(如打印前置log等)。

2. 生成的实例对象是针对target对象的“拦截器”。也可以叫做“代理器”。

3. 然后必须通过操作proxy,即“拦截器”(拦截器对象本身性质和目标对象target一样,比如:target是函数,

那么proxy也是函数)才能触发拦截方法,来完成对目标对象的操作和访问。

4. 如果handler是个空对象({}),那么操作拦截器相当于直接操作目标对象target。

3. Proxy构造函数的特征

1. Proxy函数没有prototype属性,所以也就不能使用instanceof判断是否是Proxy实例

2. Proxy实例的数据类型和target数据类型一致。

var proxy1 = new Proxy([], {});
proxy1 instanceof Array; // true
var proxy2 = new Proxy(function(){}, {});
typeof proxy2; //"function"

二. Proxy拦截器handler方法

一共有13个拦截方法(对应Reflect的13个方法),可以大体分为两个部分。

1. 新的方法名

返回值是布尔值的方法有:

1. has(target, propKey)

作用:  拦截判断target对象是否含有属性propKey的操作

拦截操作: propKey in proxy;   不包含for...in循环

对应Reflect: Reflect.has(target, propKey)

语法:

const handler = {
    has(target, propKey){
        // ...
        //默认操作
        return propKey in target;
    }
}
const proxy = new Proxy(target, handler)

示例: 过滤某些私有属性

  const handler = {
    has(target, propKey) {
      if (propKey[0] === ‘_‘) {
        return false;
      }
      return propKey in target;
    }
  }
  const target = {_myprop: 1, a: 2, c:3};
  const proxy = new Proxy(target, handler);
  ‘_myprop‘ in proxy; // false

特殊情况: 目标target是不可扩展或者某个属性不可配置,只能返回默认行为结果;否则报错

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false; //只能是return prop in target;
  }
});
‘a‘ in p; //Uncaught TypeError: ‘has‘ on proxy: trap returned falsish for property ‘a‘ but the proxy target is not extensible

2. deleteProperty(target, propKey)

作用: 拦截删除target对象的propKey属性的操作

拦截操作: delete proxy[propKey]

语法: 

const handler = {
    deleteProperty(target, propKey){
        // ...
        //默认操作
        delete target[propKey];
        return true; //!!!严格模式下必须有,否则报错;
    }
}
const proxy = new Proxy(target, handler)

示例:

var obj = { a: 10 };
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    console.log(‘delete propName ‘,prop);
   if
delete target[prop]; return true; } }); delete p.a; console.log(obj); // 运行结果如下: ‘delete propName a‘ {}

特殊情况: 属性是不可配置属性时,不能删除; 但是对象不可扩展的时候,可以删除属性。

var obj = { a: 10 };
Object.defineProperty(obj, ‘b‘, {
  value: 2, configurable: false
});
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    delete target[prop];
    return true;
  }
});
delete p.b; // Uncaught TypeError: Cannot delete property ‘b‘ of #<Object>
console.log(obj);

3. ownKeys(target)

返回: 数组(数组元素必须是字符或者Symbol,其他类型报错)

作用: 拦截获取键值的操作

拦截操作: Object.getOwnPropertyNames(proxy)

                Object.getOwnPropertySymbols(proxy)

                Object.keys(proxy)

                for...in...循环

语法: 

最后取到的结果不是return的值,而是会自动过滤

const handler = {
   ownKeys(target) {
// 所有的keys;也可以是其他的数组
return Reflect.ownKeys(target); } }

示例: 

var obj = { a: 10, [Symbol.for(‘foo‘)]: 2 };
Object.defineProperty(obj, ‘c‘, {
  value: 3, enumerable: false
})
var p = new Proxy(obj, {
  ownKeys(target) {
    return [...Reflect.ownKeys(target), ‘b‘, Symbol.for(‘bar‘)];
  }
});
const keys = Object.keys(p);  // [‘a‘]
// 自动过滤掉Symbol/非自身/不可遍历的属性

for(let prop in p) { // 和Object.keys()过滤性质一样,只返回target本身的可遍历属性
  console.log(‘prop-‘,prop); // prop-a
}
const ownNames = Object.getOwnPropertyNames(p);// [‘a‘, ‘c‘, ‘b‘]
// 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性

const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)]
// 只返回拦截器返回的Symbol的属性,不管是不是target上的属性

const ownKeys = Reflect.ownKeys(p);// [‘a‘,‘c‘,Symbol(foo),‘b‘,Symbol(bar)]
// 返回拦截器返回的所有值

特殊情况:

1)如果某个属性是不可配置的,那么该属性在拦截器中必须被返回,否则报错

2)如果target对象是不可扩展的,那么拦截器返回的数组必须是操作的默认返回结果。

即必须是被拦截的操作的默认行为,如getOwnPropertyNames()

4. apply(target, thisArg, args)--target是函数

返回:函数执行结果

作用: 拦截proxy作为函数调用时的操作

拦截操作:proxy()

               proxy.call(obj, ...args)

               proxy.apply(obj, [...args])

               Reflect.apply(proxy, thisArg, args)

语法:

const handler = {
  apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
    return Reflect.apply(...arguments);
  }
}

示例:

const handler = {
  apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
    console.log(‘---apply拦截器---‘,contextThis,‘-‘,args);
    return Reflect.apply(...arguments)+‘end‘;
  }
}
let target = function(a,b) {
  return a+b;
}
const proxy = new Proxy(target, handler);
console.log(‘proxy-->‘,proxy(5,6));
console.log(‘proxy.call-->‘,proxy.call(null, 5, 6));
console.log(‘proxy.apply-->‘,proxy.apply(null, [5,6]));
console.log(‘Reflect-->‘,Reflect.apply(proxy, null, [5,6]));

// 运行结果如下:
/*
---apply拦截器---undefined-[5,6]
proxy-->11end
---apply拦截器---null-[5,6]
proxy.call-->11end
---apply拦截器---null-[5,6]
proxy.apply-->11end
---apply拦截器---null-[5,6]
Reflect-->11end
*/

5. construct(target, args, newTarget)--target是构造函数

返回: 实例对象, 不是对象会报错 

作用: 拦截new命令

拦截操作: new proxy(...args)

语法:

const handler = {
  construct(target, args, newTarget){// args是参数数组, newTarget是生成的proxy实例
    console.log(‘----拦截new----‘,args,‘-‘, newTarget === proxy);
    return Reflect.construct(target, args);// 默认行为,也可以return {a: b},只要是对象就可以
  }
}
const target = function(a,b) {};
var proxy = new Proxy(target, handler);
console.log(‘proxy type-->‘, typeof proxy);
const result = new proxy(1,2); // 触发拦截器
console.log(‘result type-->‘,typeof result)
// 运行结果如下: proxy type-->function ----拦截new----[1,2]-true result type-->object

2. 方法名和对象原有方法名一样

6. get(target, propKey, receiver)

返回: 返回读取的属性

作用:拦截对象属性的读取

拦截操作:proxy[propKey]或者点运算符

语法:

const handler = {
  get(target, propKey, receiver) {// receiver是proxy实例
    return Reflect.get(target, propKey);
  }
}

示例: 实现函数的链式操作

const funsObj = {
  double(n) {return n*2},
  square(n) {return n**2}
}
var pipe = (value) => {
  const callStack=[];
  return new Proxy({}, {
    get(target, propKey, receiver) {
      console.log(propKey,callStack);
      if (propKey === ‘get‘) {
        return callStack.reduce((val, fn) => {
          return fn(val);
        }, value)
      } else {
        callStack.push(funsObj[propKey]);
      }
      return receiver; //返回proxy实例才能实现链式调用
    }
  })
}
pipe(3).double.square.get; //36

get方法可以继承,但是receiver的值会是直接触发的那个对象。

const proxy= new Proxy({}, {
  get(target, propKey, receiver) {
    return receiver;
  }
})
const p = Object.create(proxy);
console.log(p.a === p); // true p.a返回receiver

特殊情况:如果对象的属性writable和configurable同时为false, 则拦截器只能返回默认行为

const target = {};
Object.defineProperty(target, ‘a‘, {
  value: 1, 
  writable: false,
  configurable: false
})
const proxy = new Proxy(target, {
  get(target,propKey) {
    return 2;
    //应该return 1;不能返回其他值,否则报错
  }
})
proxy.a; // Uncaught TypeError

7.set(target,propKey, value,receiver)

作用: 拦截对象的属性赋值操作

拦截操作: proxy[propkey] = value

语法:

const proxy = new Proxy({}, {
  set(target, propKey, value,receiver) {// receiver时proxy实例
    Reflect.set(...arguments); //当Reflect.set传入的参数有receiver且属性writable=true时,会在receiver在定义属性,会触发defineProperty拦截
    return true; //!!!严格模式下必须有,否则报错;非严格模式下可以省略
  },
  defineProperty(target, propKey, propDesc) {
    console.log(‘defineProperty‘)
  }
})

set方法也可以继承,receiver也是最终调用的那个实例,和get方法一样。参照get的方法。

设置 target[propKey] = receiver;

当对象的属性writable为false时,该属性不能在拦截器中被修改;

const obj = {};
Object.defineProperty(obj, ‘foo‘, {
  value: ‘bar‘,
  writable: false,
  configurable: true,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    Reflect.set(...arguments);
    return true;
  },
};
const proxy = new Proxy(obj, handler);
proxy.foo = ‘baz‘;
console.log(obj); // {foo: bar} 说明修改失败

8. defineProperty(target, propKey,PropDesc)

返回: 严格模式下必须返回true

作用:拦截定义属性或者重新定义属性操作

拦截操作: Object.defineProperty(proxy, propKey,propDesc)

                Reflect.defineProperty(proxy, propKey,propDesc)

语法兼示例:

const proxy = new Proxy({}, {
  defineProperty(target, propKey, propDescriptor) {// 最后一个参数是属性描述对象
    Reflect.defineProperty(...arguments);
    return true;// !!严格模式下必须返回true,否则报错
  }
})
Object.defineProperty(proxy, ‘a‘, {value:5})

特殊情况:

如果对象是不可扩展的(preventExtensible(obj)),则不能添加属性;

如果对象的某属性writable和configurable同时为false, 则不能重新定义该属性的值;

如果上面的属性有其中一个是true,可以重新定义该属性的值。

9. getPrototypeOf(target)

返回: 返回对象或者null,否则报错

作用:拦截获取原型对象的操作

拦截操作:Object.getPrototypeOf(proxy)

               proxy.__proto__

              Object.isPrototypeOf(proxy)

              Reflect.getPrototypeOf(proxy)

              instanceof

语法:

var p = new Proxy({}, {
  getPrototypeOf(target) {
    return Reflect.getPrototypeOf(target);
  }
});

示例:

var proxy = new Proxy({}, {
  getPrototypeOf(target) {
    return {a:1}
  }
});
Object.getPrototypeOf(proxy); // {a:1} 

特殊情况:

如果目标对象是不可扩展的,那么只能返回默认的原型对象。

10.setPrototypeOf(target, proto)

返回:严格模式下返回true,否则报错;只能是布尔值

作用:拦截设置原型对象的操作

拦截操作:Object.setPrototypeOf(proxy, proto)

               proxy.__proto__

               Reflect.setPropertyOf(proxy,proto)

语法:

var proxy = new Proxy({}, {
  setPrototypeOf(target, proto) {
    Reflect.setPrototypeOf(...arguments);
    return true; // !!!严格模式下必须是true;否则报错
  }
})

特殊情况:

如果对象不可扩展,只能进行默认行为。不能修改。

11. isExtensible(target)---只能返回默认行为结果

返回:布尔值

 作用: 拦截是否可扩展方法的调用

拦截操作: Object.isExtensible(proxy)

                Reflect.isExtensible(proxy)

语法:

const proxy = new Proxy({}, {
  isExtensible(target) {
    return Reflect.isExtensible(target); //!!!返回结果只能是这个;即proxy和target的可扩展性必须是一致的
  }
})

12. preventExtensible(target)--遵循默认行为

返回:严格模式下返回true,否则报错;

拦截操作:Object.preventExtensible(proxy)

               Reflect.preventExtentsible(proxy)

语法:

const proxy = new Proxy({}, {
  preventExtensions(target) {
    Reflect.preventExtensions(target); //严格模式下,必须存在
    return true; // 严格模式下必须存在
  }
})

13. getOwnPropertyDescriptor(target, propKey)

返回: 对象或者undefined

拦截操作: Object.getOwnPropertyDescriptor(proxy)

                Reflect.getOwnPropertyDescriptor(proxy)

语法:

const proxy = new Proxy({}, {
  getOwnPropertyDescriptor(target, propKey) {
    return Reflect.getOwnPropertyDescriptor(target,propKey);
  }
})

三. 静态方法-Proxy.revocable()

作用: 返回一个可取消的proxy实例

语法:

const {proxy, revoke} = Proxy.revocable(target,handler);
// proxy是实例
// revoke是个方法;直接调用后取消proxy实例

示例:

const {proxy, revoke} = Proxy.revocable({},{});
proxy.a=5;
revoke();
proxy.a // Uncaught TypeError: Cannot perform ‘get‘ on a proxy that has been revoked

应用: 

用于一个只能访问一次的代理器

四. this指向

拦截器方法中的this指向proxy实例

五.Proxy应用

观察者模式:通过拦截对象的赋值操作,实时调用观察函数。

 

以上是关于Proxy详解的主要内容,如果未能解决你的问题,请参考以下文章

1. Proxy使用详解

(转) Java中的负数及基本类型的转型详解

Proxy详解

详解Android WebView加载html片段

Zabbix Server Active Proxy Trappe RCE 漏洞详解(CVE-2017-2824)

nginx proxy_cache缓存详解