首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Proxy

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

术语

handler包含陷阱(traps)的占位符对象。

traps提供属性访问的方法。这类似于操作系统中陷阱的概念。

target代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

语法

代码语言:javascript
复制
var p = new Proxy(target, handler);

参数

targetProxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler一个对象,其属性是当执行一个操作时定义代理的行为的函数。

方法

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

handler 对象的方法

handler 对象是一个占位符对象,它包含Proxy的traps。

所有的陷阱都是可选的。如果没有定义陷阱,则默认行为是将操作转发到目标。

handler.getPrototypeOf()A trap for Object.getPrototypeOf.

handler.setPrototypeOf()A trap for Object.setPrototypeOf.

handler.isExtensible()A trap for Object.isExtensible.

handler.preventExtensions()A trap for Object.preventExtensions.

handler.getOwnPropertyDescriptor()A trap for Object.getOwnPropertyDescriptor.

handler.defineProperty()A trap for Object.defineProperty.

handler.has()A trap for the in operator.

handler.get()A trap for getting property values.

handler.set()A trap for setting property values.

handler.deleteProperty()A trap for the delete operator.

handler.ownKeys()A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

handler.apply()A trap for a function call.

handler.construct()A trap for the new operator.

一些非标准的陷阱已经过时,已被删除。

示例

基础示例

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

代码语言:javascript
复制
var handler = {
    get: function(target, name) {
        return name in target ?
            target[name] :
            37;
    }
};

var 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 对象,代理会将所有应用到它的操作转发到这个对象上。

代码语言:javascript
复制
var target = {};
var p = new Proxy(target, {});

p.a = 37; // operation forwarded to the target

console.log(target.a); // 37. The operation has been properly forwarded

验证

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

代码语言:javascript
复制
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;

    // Indicate success
    return true;
  }
};

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

person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Throws an exception
person.age = 300; // Throws an exception

扩展构造函数

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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;

    // Indicate success
    return true;
  }
});

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。

代码语言:javascript
复制
let products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
},
{
  get: function(obj, prop) {
    // An extra property
    if (prop === 'latestBrowser') {
      return obj.browsers[obj.browsers.length - 1];
    }

    // The default behavior to return the value
    return obj[prop];
  },
  set: function(obj, prop, value) {
    // An extra property
    if (prop === 'latestBrowser') {
      obj.browsers.push(value);
      return true;
    }

    // Convert the value if it is not an array
    if (typeof value === 'string') {
      value = [value];
    }

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

    // Indicate success
    return true;
  }
});

console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // pass a string (by mistake)
console.log(products.browsers); // ['Firefox'] <- no problem, the value is an array

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

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

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

代码语言:javascript
复制
let products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
],
{
  get: function(obj, prop) {
    // The default behavior to return the value; prop is usually an integer
    if (prop in obj) {
      return obj[prop];
    }

    // Get the number of products; an alias of 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];
      }
    }

    // Get a product by name
    if (result) {
      return result;
    }

    // Get products by type
    if (prop in types) {
      return types[prop];
    }

    // Get product types
    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全局对象。

代码语言:javascript
复制
/*
  var docCookies = ... get the "docCookies" object here:  
  https://developer.mozilla.org/en-US/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 test */

console.log(docCookies.my_cookie1 = 'First value');
console.log(docCookies.getItem('my_cookie1'));

docCookies.setItem('my_cookie1', 'Changed value');
console.log(docCookies.my_cookie1);

规范

Specification

Status

Comment

ECMAScript 2015 (6th Edition, ECMA-262)The definition of 'Proxy' in that specification.

Standard

Initial definition.

ECMAScript 2016 (ECMA-262)The definition of 'Proxy' in that specification.

Standard

ECMAScript 2017 (ECMA-262)The definition of 'Proxy' in that specification.

Standard

ECMAScript Latest Draft (ECMA-262)The definition of 'Proxy' in that specification.

Living Standard

浏览器支持

Feature

Chrome

Edge

Firefox (Gecko)

Internet Explorer

Opera

Safari

Basic support

49.0

12 (10240)

18 (18)

No support

36

10.0

Feature

Android

Chrome for Android

Edge

Firefox Mobile (Gecko)

IE Mobile

Opera Mobile

Safari Mobile

Basic support

56

49.0

(Yes)

18 (18)

13 (10586)

37

10.0

扫码关注腾讯云开发者

领取腾讯云代金券