Proxy
Proxy
Proxy
对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
术语
handler包含陷阱(traps)的占位符对象。
语法
var p = new Proxy(target, handler
参数
target
用Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
方法
Proxy.revocable()
创建一个可撤销的Proxy
对象。
handler 对象的方法
handler 对象是一个占位符对象,它包含Proxy
的traps。
所有的陷阱都是可选的。如果没有定义陷阱,则默认行为是将操作转发到目标。
handler.getPrototypeOf()
A trap for Object.getPrototypeOf
.
一些非标准的陷阱已经过时,已被删除。
示例
基础示例
在以下简单的例子中,当对象中不存在属性名时,缺省返回数为37
。例子中使用了 get
。
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 对象,代理会将所有应用到它的操作转发到这个对象上。
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。
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
扩展构造函数
方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了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;
// 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。
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。
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全局对象。
/*
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 |