Proxy如其名, 它的作用是在对象和和对象的属性值之间设置一个代理,获取该对象的值或者设置该对象的值, 以及实例化等等多种操作, 都会被拦截住, 经过这一层我们可以统一处理,我们可以认为它就是“代理器” ;

下面是代理的一些术语:

target

目标对象,可以是JavaScript对象(如jQuery库),也可以是原生对象(如数组),甚至可以是另一个代理。

handler

实现代理行为的对象。

traps

提供属性访问的方法。

一个简单的案例:

const target = {
  a: 1,
  b: 2,
  c: 3
};

我们创建一个代理对象,实现对目标对象的get操作符的拦截,如果目标对象能访问这个属性,直接返回属性值,否则返回一个42:

const handler = {
  get: function(target, name) {
    return (
      name in target ? target[name] : 42
    );
  }
};

现在创建一个代理对象,可以直接通过Proxy访问目标对象:

const proxy = new Proxy(target, handler);

console.log(proxy.a);  // 1
console.log(proxy.b);  // 2
console.log(proxy.c);  // 3
console.log(proxy.meaningOfLife);  // 42

应用场景-Profiling

代理允许您创建通用包装器中的任何对象,而无需更改代码的目标对象本身。

在这个例子中,我们将统计代理的属性被访问的次数:

// create a profiling Proxy
function makeProfiler(target) {

  const
    count = {},
    handler = {

      get: function(target, name) {
        if (name in target) {
          count[name] = (count[name] || 0) + 1;
          return target[name];
        }
      }
    };

  return {
    proxy: new Proxy(target, handler),
    count: count
  }
};

我们现在可以使用这个代理包装任何对象。例如:

const myObject = {
  h: 'Hello',
  w: 'World'
};

// create a myObject proxy
const pObj = makeProfiler(myObject);

// access properties
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.w); // World
console.log(pObj.count.h); // 2
console.log(pObj.count.w); // 1

可以想象,如果不使用代理的话,想要实现同样的功能需要写更多的代码。

应用场景-双向数据绑定

双向数据绑定,通常用于JavaScript MVC库更新内部对象时, DOM改变,反之亦然。

假设,页面由一个ID为inputname的元素:

<input type="text" id="inputname" value="" />

我们也有一个JavaScript对象引用了inputname:

// internal state for #inputname field
const myUser = {
  id: 'inputname',
  name: ''
};
inputChange(myUser);

// bind input to object
function inputChange(myObject) {
  if (!myObject || !myObject.id) return;

  const input = document.getElementById(myObject.id);
  input.addEventListener('onchange', function(e) {
    myObject.name = input.value;
  });
}

我们首先在用户输入值更新myUser.name。通过onchange事件处理程序可以实现:

inputChange(myUser);

// bind input to object
function inputChange(myObject) {
  if (!myObject || !myObject.id) return;

  const input = document.getElementById(myObject.id);
  input.addEventListener('onchange', function(e) {
    myObject.name = input.value;
  });
}

当我们通过js修改myUser.name时,同时要更新页面控件的显示值。这不是那么简单, 但是Proxy提供一个解决方案:

// proxy handler
const inputHandler = {
  set: function(target, prop, newValue) {
    if (prop == 'name' && target.id) {
      // update object property
      target[prop] = newValue;

      // update input field value
      document.getElementById(target.id).value = newValue;
      return true;
    }
    else return false;
  }
}

// create proxy
const myUserProxy = new Proxy(myUser, inputHandler);

// set a new name
myUserProxy.name = 'Craig';
console.log(myUserProxy.name); // Craig
console.log(document.getElementById('inputname').value); // Craig

这可能不是最有效的数据绑定选项, 但Proxy允许你不改变业务代码的情况下,改变现有的行为。

Proxy支持情况

目前,除了ie 11, 实现Node和所有当前浏览器都支持Proxy。

注意: 并不是所有的浏览器都支持所有的traps

不幸的是,它是不可能polyfill或transpile ES6代理代码使用工具,如Babel。因为Proxy太强大了而ES5中是无法提供等价功能的。