2

认识Proxy

可以把Proxy看作是一个拦截器,可以对目标对象的访问进行过滤和改写。proxy实例可以拦截对象的属性读取、赋值、删除、通过definedProperty定义等操作外,还可以拦截到对象被当作方法调用还是构造函数使用等操作。那Proxy具体怎么用的呢?

Proxy的用法

通过Proxy来监测对象的赋值和读取

入门例子:给目标对象添加属性并赋值

var obj = new Proxy({}, {
    get: function (target, propKey, receiver) {
        console.log(`getting ${propKey} value: ${target[propKey]}!`);
        return Reflect.get(target, propKey, receiver);
    },
    set: function (target, propKey, value, receiver) {
        console.log(`setting ${propKey} value: ${value}!`);
        return Reflect.set(target, propKey, value, receiver);
    }
});
obj.count = 1
++obj.count

//setting count value: 1!
//getting count value: 1!
//setting count value: 2!

Proxy的特点

new Proxy(第一个参数, 第二个参数),第一个参数为目标对象,第二个参数为操作对象的行为的对象。

Proxy构造函数的第一个参数为目标对象,和new出来的Proxy实例是同一个对象,但要注意只有修改Proxy实例值才会触发对应的拦截的方法:

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

Proxy实例也可以做为其他对象的原型对象:

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35

可以设置多个拦截操作,例如:拦截对象作为方法使用、作为构造函数使用、作为对象操作属性等操作,可以在函数中return指定值:

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

触发Proxy实例set的情况和触发Proxy实例get的情况:

  • 触发Proxy实例set的情况:给实例添加属性或对已存在属性进行重新赋值时,无论属性的值是什么类型都会触发set。
  • 触发Proxy实例get的情况:读取Proxy实例中的属性时(无论属性是否已经存在)、除了以上触发set的方法,其他赋值行为都会触发get(具体见下面例子各种赋值情况)。
var data = {
    odata: {
        name: 'odata',
        age: '21'
    },
    arr: [{
        name: 'youyi',
        age: '24'
    }],
    name: 'bb'
}

var obsever = new Proxy(data, {
    get: function (target, propKey, receiver) {
        console.log('get:', target[propKey])
        return Reflect.get(target, propKey, receiver);
    },
    set: function (target, propKey, value, receiver) {
        console.log('set:', value)
        return Reflect.set(target, propKey, value, receiver);
    }
})

以上的结果:

赋值却触发proxy实例get方法的情况
1、对proxy实例中的数组进行操作:

image.png

image.png

image.png

2、对proxy实例中的对象的属性进行操作:
image.png

赋值触发proxy实例set方法:修改proxy实例的属性的整个值或新增一个属性并赋值时

1、修改proxy实例的数组类型属性
image.png

2、修改proxy实例的对象类型属性
image.png

Proxy的应用场景

  • 实现类似Promise链式调用
var pipe = (function () {
  return function (value) {
    var funcStack = [];
    var oproxy = new Proxy({} , {
      get : function (pipeObject, fnName) {
        if (fnName === 'get') {
          return funcStack.reduce(function (val, fn) {
            return fn(val);
          },value);
        }
        funcStack.push(window[fnName]);
        return oproxy;
      }
    });

    return oproxy;
  }
}());

var double = n => n * 2;
var pow    = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; // 63
  • 利用get拦截返回函数,实现web客户端
const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});


function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

参考资料:

ES6之proxy

抱歉,学会 Proxy 真的可以为所欲为


贝er
58 声望6 粉丝

不仅仅是程序员