原文: Javascript Proxies: Real World Use Case -- Arbaz Siddiquijavascript
译者注, 为了防止出现"鲁棒性"这种因翻译习惯差别致使的混淆, 文中部分术语将不会进行翻译.java
在编程术语范畴中, Proxy指的是帮助/替代另外一个实体(Entity)完成一系列操做的实体. 一个架设在客户端与服务端之间的Proxy服务器分别充当了客户端的服务端和服务端的客户端. 对于Proxy来讲, 它们的任务就是介入收到的请求/调用, 并在处理后传递给其上游. 这些介入容许Proxy添加一些额外的业务逻辑或者改变整个操做的行为.git
JavaScript的Proxy从某种意义上来讲是类似的. 它处在代码所操做的对象与实际被操做的对象之间进行处理.github
根据MDN Web文档shell
The Proxy is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).编程
Proxy被用来自定义一些基础层面的操做(例如属性查找, 赋值, 枚举, 函数调用等)缓存
在完成一个Proxy的使用以前, 有三个术语须要咱们提早进行了解:服务器
Target就是实际被Proxy操做修改的对象. 它能够是任何一个JavaScript对象.markdown
译者注: 这个地方的翻译说实话有点不太好翻译, 但实际上只要可以理解所谓Traps就是用来重载(代理)Target对应的名字的属性/方法的属性/方法就行ide
Traps是指那些在Target的属性或者方法被调用时会介入干涉的方法. 有许多定义了的Traps能够被实现(implement)
Handler是一个全部的Traps生存的占位对象. 简单来讲, 能够把它当作一个存放且实现各个traps的对象.
咱们来看看下面这个例子:
//movie is a target const movie = { name: "Pulp Fiction", director: "Quentin Tarantino" }; //this is a handler const handler = { //get is a trap get: (target, prop) => { if (prop === 'director') { return 'God' } return target[prop] }, set: function (target, prop, value) { if (prop === 'actor') { target[prop] = 'John Travolta' } else { target[prop] = value } } }; const movieProxy = new Proxy(movie, handler); console.log(movieProxy.director); //God movieProxy.actor = "Tim Roth"; movieProxy.actress = "Uma Thurman"; console.log(movieProxy.actor); //John Travolta console.log(movieProxy.actress); //Uma Thurman 复制代码
输出以下
God
John Travolta
Uma Thurman
复制代码
上面这个例子中, movie
就是咱们所说的Target. 咱们实现了一个拥有set
和get
这两个trap的handler
. 在其中咱们添加了两个逻辑: 在访问director
时, get
这个trap会直接返回God
而不是它实际的值; 在对actor
赋值时, set
这个trap会干涉全部的赋值操做, 并在键为actor
时将值改变成John Travlota
.
虽然并不如其余的ES2015的特性那样广为人知, Proxy仍是有诸如全部属性的默认值这样的如今看来挺亮眼的用例. 让咱们来看看其余的在真实生产环场景中可以利用Proxy的地方.
既然咱们已经能够干涉对象的属性赋值过程, 那么咱们能够借此来校验咱们将要赋予给对象属性的值. 看下面这个例子
const handler = { set: function (target, prop, value) { const houses = ['Stark', 'Lannister']; if (prop === 'house' && !(houses.includes(value))) { throw new Error(`House ${value} does not belong to allowed ${houses}`) } target[prop] = value } }; const gotCharacter = new Proxy({}, handler); gotCharacter.name = "Jamie"; gotCharacter.house = "Lannister"; console.log(gotCharacter); gotCharacter.name = "Oberyn"; gotCharacter.house = "Martell"; 复制代码
运行结果以下:
{ name: 'Jamie', house: 'Lannister' }
Error: House Martell does not belong to allowed Stark,Lannister
复制代码
上面这个例子中, 咱们严格限制了house
这个属性所能被赋予的值的范围. 只须要建立一个set
的trap, 咱们甚至能用这个实现方式来实现一个只读的对象.
咱们能够经过Proxy来建立一个在读写属性时的反作用. 出发点在于某些特定的属性被访问或者写入时触发一些函数. 看下面这个例子:
const sendEmail = () => { console.log("sending email after task completion") }; const handler = { set: function (target, prop, value) { if (prop === 'status' && value === 'complete') { sendEmail() } target[prop] = value } }; const tasks = new Proxy({}, handler); tasks.status = "complete"; 复制代码
运行结果以下:
sending email after task completion
复制代码
这里咱们干涉了status
这个属性的写入. 当写入的值是complete
时, 会触发一个反作用函数. 在Sindre Sorhus的on-change这个包中就一个很Cooooooool的实现.
利用介入干涉对象属性读写的能力, 咱们可以建立一个基于内存的缓存. 它只会在值过时前返回值. 看下面这个例子:
const cacheTarget = (target, ttl = 60) => { const CREATED_AT = Date.now(); const isExpired = () => (Date.now() - CREATED_AT) > (ttl * 1000); const handler = { get: (target, prop) => isExpired() ? undefined : target[prop] }; return new Proxy(target, handler) }; const cache = cacheTarget({age: 25}, 5); console.log(cache.age); setTimeout(() => { console.log(cache.age) }, 6 * 1000); 复制代码
运行结果以下:
25
undefined
复制代码
这里咱们建立了一个函数, 并返回一个Proxy. 在获取target的属性前, 这个Proxy的handler首先会检查target对象是否过时. 基于此, 咱们能够针对每一个键值都设置一个基于TTLs或者其余机制的过时检查.
虽然Proxy具有一些很神奇的功能, 但在使用时仍然具备一些不得不当心应对的限制:
Proxy很强, 在很大范围内都可以获得应用, 或者被滥用. 这篇文章中咱们讨论了什么是Proxy, 如何实现一个Proxy, 几个真实案例中的用例, 以及它的缺陷限制.