Proxy 对象用于定义基本操作的自定义行为（如属性查找，赋值，枚举，函数调用等）。

## 术语

handler
  包含陷阱（traps）的占位符对象。
traps
  提供属性访问的方法。这类似于操作系统中陷阱的概念。
target
  代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量（保持不变的语义）。

## 语法

    let p = new Proxy(target, handler);

### 参数

`target`
  用`Proxy`包装的目标对象（可以是任何类型的对象，包括原生数组，函数，甚至另一个代理）。
`handler`
  一个对象，其属性是当执行一个操作时定义代理的行为的函数。

## 方法

`Proxy.revocable()`
  创建一个可撤销的`Proxy`对象。

## handler 对象的方法

handler 对象是一个占位符对象，它包含`Proxy`的陷阱。

## 示例

### 基础示例

在以下简单的例子中，当对象中不存在属性名时，缺省返回数为`37`。例子中使用了 `get`。

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

    let p = new Proxy({}, handler);

    p.a = 1;
    p.b = undefined;

    console.log(p.a, p.b);    // 1, undefined

    console.log('c' in p, p.c);    // false, 37

### 无操作转发代理

在以下例子中，我们使用了一个原生 JavaScript 对象，代理会将所有应用到它的操作转发到这个对象上。

    let target = {};
    let p = new Proxy(target, {});

    p.a = 37;   // 操作转发到目标

    console.log(target.a);    // 37. 操作已经被正确地转发

### 验证

通过代理，你可以轻松地验证向一个对象的传值。这个例子使用了 `set`。

    let validator = {
      set: function(obj, prop, value) {
        if (prop === 'age') {
          if (!Number.isInteger(value)) {
            throw new TypeError('The age is not an integer');
          }
          if (value > 200) {
            throw new RangeError('The age seems invalid');
          }
        }

        // The default behavior to store the value
        obj[prop] = value;
      }
    };

    let person = new Proxy({}, validator);

    person.age = 100;

    console.log(person.age);
    // 100

    person.age = 'young';
    // 抛出异常: Uncaught TypeError: The age is not an integer

    person.age = 300;
    // 抛出异常: Uncaught RangeError: The age seems invalid

### 扩展构造函数

方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了`construct`和`apply`。

    function extend(sup,base) {
      var descriptor = Object.getOwnPropertyDescriptor(
        base.prototype,"constructor"
      );
      base.prototype = Object.create(sup.prototype);
      var handler = {
        construct: function(target, args) {
          var obj = Object.create(base.prototype);
          this.apply(target,obj,args);
          return obj;
        },
        apply: function(target, that, args) {
          sup.apply(that,args);
          base.apply(that,args);
        }
      };
      var proxy = new Proxy(base,handler);
      descriptor.value = proxy;
      Object.defineProperty(base.prototype, "constructor", descriptor);
      return proxy;
    }

    var Person = function(name){
      this.name = name
    };

    var Boy = extend(Person, function(name, age) {
      this.age = age;
    });

    Boy.prototype.sex = "M";

    var Peter = new Boy("Peter", 13);
    console.log(Peter.sex);  // "M"
    console.log(Peter.name); // "Peter"
    console.log(Peter.age);  // 13

### 操作 DOM 节点

有时你希望切换两个不同的元素的属性或类名。下面展示了如何使用 `set`。

    let view = new Proxy({
      selected: null
    },
    {
      set: function(obj, prop, newval) {
        let oldval = obj[prop];

        if (prop === 'selected') {
          if (oldval) {
            oldval.setAttribute('aria-selected', 'false');
          }
          if (newval) {
            newval.setAttribute('aria-selected', 'true');
          }
        }

        // The default behavior to store the value
        obj[prop] = newval;
      }
    });

    let i1 = view.selected = document.getElementById('item-1');
    console.log(i1.getAttribute('aria-selected')); // 'true'

    let i2 = view.selected = document.getElementById('item-2');
    console.log(i1.getAttribute('aria-selected')); // 'false'
    console.log(i2.getAttribute('aria-selected')); // 'true'

### 值修正及附加属性

以下`products`代理会计算传值并根据需要转换为数组。这个代理对象同时支持一个叫做 `latestBrowser`的附加属性，这个属性可以同时作为 getter 和 setter。

    let products = new Proxy({
      browsers: ['Internet Explorer', 'Netscape']
    },
    {
      get: function(obj, prop) {
        // 附加属性
        if (prop === 'latestBrowser') {
          return obj.browsers[obj.browsers.length - 1];
        }

        // 缺省行为是返回属性值
        return obj[prop];
      },
      set: function(obj, prop, value) {
        // 附加属性
        if (prop === 'latestBrowser') {
          obj.browsers.push(value);
          return;
        }

        // 如果不是数组则进行转换
        if (typeof value === 'string') {
          value = [value];
        }

        // 缺省行为是保存属性值
        obj[prop] = value;
      }
    });

    console.log(products.browsers); // ['Internet Explorer', 'Netscape']
    products.browsers = 'Firefox'; // ?传入一个 string (错误地)
    console.log(products.browsers); // ['Firefox'] <- ?没问题, ?得到的是一个 array

    products.latestBrowser = 'Chrome';
    console.log(products.browsers); // ['Firefox', 'Chrome']
    console.log(products.latestBrowser); // 'Chrome'

### 通过属性查找数组中的特定对象

以下代理为数组扩展了一些实用工具。可以看到，你可以灵活地“定义”属性，而不需要使用 `Object.defineProperties`方法。以下例子可以用于通过单元格来查找表格中的一行。在这种情况下，target 是`table.rows`。

    let products = new Proxy([
      { name: 'Firefox', type: 'browser' },
      { name: 'SeaMonkey', type: 'browser' },
      { name: 'Thunderbird', type: 'mailer' }
    ],
    {
      get: function(obj, prop) {
        // 缺省行为是返回属性值， prop ?通常是一个整数
        if (prop in obj) {
          return obj[prop];
        }

        // 获取 products 的 number; 它是 products.length 的别名
        if (prop === 'number') {
          return obj.length;
        }

        let result, types = {};

        for (let product of obj) {
          if (product.name === prop) {
            result = product;
          }
          if (types[product.type]) {
            types[product.type].push(product);
          } else {
            types[product.type] = [product];
          }
        }

        // 通过 name 获取 product
        if (result) {
          return result;
        }

        // 通过 type 获取 products
        if (prop in types) {
          return types[prop];
        }

        // 获取 product type
        if (prop === 'types') {
          return Object.keys(types);
        }

        return undefined;
      }
    });

    console.log(products[0]); // { name: 'Firefox', type: 'browser' }
    console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
    console.log(products['Chrome']); // undefined
    console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
    console.log(products.types); // ['browser', 'mailer']
    console.log(products.number); // 3

### 一个完整的 `traps` 列表示例

出于教学目的，这里为了创建一个完整的 traps 列表示例，我们将尝试代理化一个非原生对象，这特别适用于这类操作：由 发布在 document.cookie页面上的“小型框架”创建的`docCookies`全局对象。

    /*
      var docCookies = ... get the "docCookies" object here:
      https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
    */

    var docCookies = new Proxy(docCookies, {
      "get": function (oTarget, sKey) {
        return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
      },
      "set": function (oTarget, sKey, vValue) {
        if (sKey in oTarget) { return false; }
        return oTarget.setItem(sKey, vValue);
      },
      "deleteProperty": function (oTarget, sKey) {
        if (sKey in oTarget) { return false; }
        return oTarget.removeItem(sKey);
      },
      "enumerate": function (oTarget, sKey) {
        return oTarget.keys();
      },
      "ownKeys": function (oTarget, sKey) {
        return oTarget.keys();
      },
      "has": function (oTarget, sKey) {
        return sKey in oTarget || oTarget.hasItem(sKey);
      },
      "defineProperty": function (oTarget, sKey, oDesc) {
        if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
        return oTarget;
      },
      "getOwnPropertyDescriptor": function (oTarget, sKey) {
        var vValue = oTarget.getItem(sKey);
        return vValue ? {
          "value": vValue,
          "writable": true,
          "enumerable": true,
          "configurable": false
        } : undefined;
      },
    });

    /* Cookies 测试 */

    alert(docCookies.my_cookie1 = "First value");
    alert(docCookies.getItem("my_cookie1"));

    docCookies.setItem("my_cookie1", "Changed value");
    alert(docCookies.my_cookie1);

## 规范

Specification | Status | Comment
--- | --- | ---
ECMAScript 2015 (6th Edition, ECMA-262) Proxy | Standard | Initial definition.
ECMAScript 2016 (ECMA-262) Proxy | Standard |
ECMAScript 2017 (ECMA-262) Proxy | Standard |
ECMAScript Latest Draft (ECMA-262) Proxy | Draft |

## 浏览器兼容性

The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

| Desktop | Mobile | Server
| Chrome | Edge | Firefox | Internet Explorer | Opera | Safari | Android webview | Chrome for Android | Edge Mobile | Firefox for Android | Opera for Android | Safari on iOS | Samsung Internet | Node.js
`Proxy` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`revocable` | Chrome Full support 63 | Edge Full support 12 | Firefox Full support 34 | IE No support No | Opera Full support Yes | Safari Full support 10 | WebView Android Full support 63 | Chrome Android Full support 63 | Edge Mobile Full support Yes | Firefox Android Full support 34 | Opera Android Full support Yes | Safari iOS Full support 10 | Samsung Internet Android Full support Yes | nodejs Full support 6.0.0
`handler.apply` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.construct` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.defineProperty` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.deleteProperty` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.enumerate` Deprecated Non-standard | Chrome No support No | Edge No support No | Firefox No support 37 — 47 | IE No support No | Opera No support No | Safari No support No | WebView Android No support No | Chrome Android No support No | Edge Mobile No support No | Firefox Android No support 37 — 47 | Opera Android No support No | Safari iOS No support No | Samsung Internet Android No support No | nodejs No support No
`handler.get` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.getOwnPropertyDescriptor` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.getPrototypeOf` | Chrome No support No | Edge No support No | Firefox Full support 49 | IE No support No | Opera No support No | Safari No support No | WebView Android No support No | Chrome Android No support No | Edge Mobile No support No | Firefox Android Full support 49 | Opera Android No support No | Safari iOS No support No | Samsung Internet Android No support No | nodejs Full support 6.0.0
`handler.has` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.isExtensible` | Chrome ? | Edge ? | Firefox Full support 31 | IE No support No | Opera ? | Safari ? | WebView Android ? | Chrome Android ? | Edge Mobile ? | Firefox Android Full support 31 | Opera Android ? | Safari iOS ? | Samsung Internet Android ? | nodejs Full support 6.0.0
`handler.ownKeys` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 Notes Full support 18 Notes Notes In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore. | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 Notes Full support 18 Notes Notes In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore. | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.preventExtensions` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 22 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 22 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.set` | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Edge Mobile Full support Yes | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0
`handler.setPrototypeOf` | Chrome ? | Edge ? | Firefox Full support 49 | IE No support No | Opera ? | Safari ? | WebView Android ? | Chrome Android ? | Edge Mobile ? | Firefox Android Full support 49 | Opera Android ? | Safari iOS ? | Samsung Internet Android ? | nodejs Full support 6.0.0

### Legend

Full support
  Full support
No support
  No support
Compatibility unknown
  Compatibility unknown
Non-standard. Expect poor cross-browser support.
  Non-standard. Expect poor cross-browser support.
Deprecated. Not for use in new websites.
  Deprecated. Not for use in new websites.
See implementation notes.
  See implementation notes.

## 参考

- "Proxies are awesome" Brendan Eich presentation at JSConf (slides)
- ECMAScript Harmony Proxy proposal page and ECMAScript Harmony proxy semantics page
- Tutorial on proxies
- SpiderMonkey specific Old Proxy API
- `Object.watch()` is a non-standard feature but has been supported in Gecko for a long time.

## 版权声明

一些内容（如文本、例子）是复制自或修改自ECMAScript wiki（版权声明 CC 2.0 BY-NC-SA）。