Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
let p = new Proxy(target, handler);
target
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler
Proxy.revocable()
Proxy
对象。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
有时你希望切换两个不同的元素的属性或类名。下面展示了如何使用 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 |
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 | 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 | 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
| 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.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 |
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)。