用计算术语来说,代理位于您和您正在与之通信的事物之间。 该术语最常用于代理服务器-网页所在的Web浏览器(Chrome,Firefox,Safari,Edge等)与网页服务器(Apache,Nginx,IIS等)之间的设备。 代理服务器可以修改请求和响应。 例如,它可以通过缓存常规访问的资产并将其提供给多个用户来提高效率。
ES6代理位于您的代码和对象之间。 代理使您可以执行元编程操作,例如侦听检查或更改对象属性的调用。
关于ES6代理使用以下术语:
目标
代理将虚拟化的原始对象。 这可能是JavaScript对象(例如jQuery库)或本机对象(例如数组或什至其他代理)。
处理程序
使用...实现代理行为的对象
陷阱
处理程序中定义的函数,可在调用特定属性或方法时提供对目标的访问。
最好用一个简单的例子来解释。 我们将创建一个名为target
的目标对象,该对象具有三个属性:
const target = {
a: 1,
b: 2,
c: 3
};
现在,我们将创建一个处理程序对象,该对象将拦截所有get
操作。 当目标可用时,返回目标的属性,否则返回42:
const handler = {
get: function(target, name) {
return (
name in target ? target[name] : 42
);
}
};
现在,我们通过传递目标和处理程序对象来创建新的代理。 我们的代码可以与代理交互,而不是直接访问target
对象:
const proxy = new Proxy(target, handler);
console.log(proxy.a); // 1
console.log(proxy.b); // 2
console.log(proxy.c); // 3
console.log(proxy.meaningOfLife); // 42
让我们进一步扩展代理处理程序,使其仅允许设置从a
到z
单字符属性:
const handler = {
get: function(target, name) {
return (name in target ? target[name] : 42);
},
set: function(target, prop, value) {
if (prop.length == 1 && prop >= 'a' && prop <= 'z') {
target[prop] = value;
return true;
}
else {
throw new ReferenceError(prop + ' cannot be set');
return false;
}
}
};
const proxy = new Proxy(target, handler);
proxy.a = 10;
proxy.b = 20;
proxy.ABC = 30;
// Exception: ReferenceError: ABC cannot be set
代理陷阱类型
我们已经看到get
和set
付诸实践,这可能是最有用的陷阱。 但是,您可以使用其他几种陷阱类型来补充代理处理程序代码:
- 构造(target,argList)
使用new
运算符捕获新对象的创建。 - 获取(目标,属性)
陷阱Object.get()
并且必须返回属性的值。 - 设置(目标,属性,值)
陷阱Object.set()
并必须设置属性值。 如果成功,则返回true
。 在严格模式下,返回false
将引发TypeError异常。 - deleteProperty(目标,属性)
捕获对对象属性的delete
操作。 必须返回true
或false
。 - apply(target,thisArg,argList)
陷阱对象函数调用。 - 具有(目标,属性)
陷阱in
运营商和必须返回true
或false
。 - ownKeys(目标)
陷阱Object.getOwnPropertyNames()
并且必须返回一个可枚举的对象。 - getPrototypeOf(目标)
陷阱Object.getPrototypeOf()
并且必须返回原型的对象或null。 - setPrototypeOf(目标,原型)
陷阱Object.setPrototypeOf()
设置原型对象。 没有返回值。 - isExtensible(目标)
陷阱Object.isExtensible()
,确定对象是否可以添加新属性。 必须返回true
或false
。 - preventExtensions(目标)
陷阱Object.preventExtensions()
,以防止将新属性添加到对象。 必须返回true
或false
。 - getOwnPropertyDescriptor(目标,属性)
陷阱Object.getOwnPropertyDescriptor()
,它返回未定义的或具有value
,writable
,get
,set
,可configurable
和enumerable
属性的属性描述符对象。 - defineProperty(目标,属性,描述符)
陷阱定义或修改对象属性的Object.defineProperty()
。 如果成功定义了目标属性,则必须返回true
否则,必须返回false
。
代理示例1:分析
代理允许您为任何对象创建通用包装,而不必更改目标对象本身内的代码。
在此示例中,我们将创建一个配置代理,该代理计算访问属性的次数。 首先,我们需要一个makeProfiler
工厂函数,该函数返回Proxy
对象并保留count状态:
// create a profiling Proxy
function makeProfiler(target) {
const
count = {},
handler = {
get: function(target, name) {
if (name in target) {
count[name] = (count[name] || 0) + 1;
return target[name];
}
}
};
return {
proxy: new Proxy(target, handler),
count: count
}
};
现在,我们可以将此代理包装器应用于任何对象或另一个代理。 例如:
const myObject = {
h: 'Hello',
w: 'World'
};
// create a myObject proxy
const pObj = makeProfiler(myObject);
// access properties
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.w); // World
console.log(pObj.count.h); // 2
console.log(pObj.count.w); // 1
尽管这是一个简单的示例,但可以想象一下如果必须在不使用代理的情况下对多个不同对象执行属性访问计数时所涉及的工作。
代理示例2:双向数据绑定
数据绑定同步对象。 当DOM更改时,通常在JavaScript MVC库中使用它来更新内部对象,反之亦然。
假设我们有一个输入字段,其ID为inputname
:
<input type="text" id="inputname" value="" />
我们还有一个名为myUser
的JavaScript对象,其id
属性引用此输入:
// internal state for #inputname field
const myUser = {
id: 'inputname',
name: ''
};
我们的第一个目标是在用户更改输入值时更新myUser.name
。 这可以通过在现场使用onchange
事件处理程序来实现:
inputChange(myUser);
// bind input to object
function inputChange(myObject) {
if (!myObject || !myObject.id) return;
const input = document.getElementById(myObject.id);
input.addEventListener('onchange', function(e) {
myObject.name = input.value;
});
}
我们的下一个目标是在JavaScript代码中修改myUser.name
时更新输入字段。 这不是那么简单,但是代理提供了一个解决方案:
// proxy handler
const inputHandler = {
set: function(target, prop, newValue) {
if (prop == 'name' && target.id) {
// update object property
target[prop] = newValue;
// update input field value
document.getElementById(target.id).value = newValue;
return true;
}
else return false;
}
}
// create proxy
const myUserProxy = new Proxy(myUser, inputHandler);
// set a new name
myUserProxy.name = 'Craig';
console.log(myUserProxy.name); // Craig
console.log(document.getElementById('inputname').value); // Craig
这可能不是最有效的数据绑定选项,但是代理允许您更改许多现有对象的行为而无需更改其代码。
进一步的例子
Hemanth.HM的文章《 JavaScript中的负数组索引》建议使用代理来实现负数组索引。 例如, arr[-1]
返回最后一个元素, arr[-2]
返回倒数第二个元素,依此类推。
Nicholas C. Zakas的文章使用ECMAScript 6代理创建类型安全属性说明了如何通过验证新值来使用代理来实现类型安全。 在上面的示例中,我们可以验证myUserProxy.name
始终设置为字符串,否则抛出错误。
代理支持
代理的功能可能不是立即显而易见的,但是它们提供了强大的元编程机会。 JavaScript的创建者Brendan Eich认为代理非常棒 !
当前,代理支持在Node和所有当前的浏览器中实现,Internet Explorer 11除外。但是,请注意,并非所有浏览器都支持所有陷阱。 通过查询“ MDN代理”页面上的此浏览器兼容性表 ,您可以更好地了解受支持的内容。
不幸的是,无法使用Babel等工具来填充或翻译ES6代理代码,因为代理功能强大且没有等效的ES5。