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; // 表示成功 return true; } }; 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; // 表示成功 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。
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; // 表示成功 return true; } }); 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 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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 | 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.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 | 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 | 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 | 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 | 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
|
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 | 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 | 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 | 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 Full support 49 | Edge No support No | Firefox Full support 49 | IE No support No | Opera Full support 36 | Safari No support No | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 49 | Opera Android Full support 36 | Safari iOS No support No | Samsung Internet Android Full support 5.0 | 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 | 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 Full support 49 | Edge Full support 12 | Firefox Full support 31 | IE No support No | Opera Full support 36 | Safari ? | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 31 | Opera Android Full support 36 | Safari iOS ? | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.ownKeys |
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 | 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.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 | 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 | 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 Full support 49 | Edge Full support 12 | Firefox Full support 49 | IE No support No | Opera Full support 36 | Safari ? | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 49 | Opera Android Full support 36 | Safari iOS ? | 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 | Firefox Android Full support 34 | Opera Android Full support Yes | Safari iOS Full support 10 | Samsung Internet Android Full support 8.0 | 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
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)。