元编程是一种强大的技术,使你可以编写能够建立其余程序的程序。ES6借助代理和许多相似功能,使在JavaScript中利用元编程变得更加容易。ES6 Proxy(代理) 有助于从新定义对象的基本操做,从而为各类可能性打开了大门。javascript
本指南能够帮助您理解为何ES6代理如此之好,尤为是对于元编程而言:html
本教程主要针对有JavaScript经验的开发人员,至少要熟悉ES6代理的概念。若是你已经对代理做为一种设计模式有了牢固的理解,那么这些知识应该能够转化为现实。前端
阅读本指南后,你应该可以:java
从根本上来讲,代理是指某件事或某人成为其余事物的替代品,因此不论是什么东西,都要通过替代品才能达到真正的交易。ES6代理的工做原理也是如此。es6
为了有效地实现和使用ES6代理,你必须了解三个关键术语:编程
综上所述,下面是最简单的实现,若是使用ES6代理,对象中不存在给定的属性,则能够返回不一样的内容。设计模式
const target = { someProp: 1 } const handler = { get: function(target, key) { return key in target ? target[key] : 'Doesn't exist!'; } } const proxy = new Proxy(target, handler); console.log(proxy.someProp) // 1 console.log(proxy.someOtherProp) // Doesn't exist!
ES6代理是一项强大的功能,可促进JavaScript中对象的虚拟化。api
因为数据绑定的复杂性,它一般很难实现。ES6代理实现双向数据绑定的应用能够在JavaScript的MVC库中看到,在这些库中,当DOM发生变化时,对象会被修改。缓存
简而言之,数据绑定是一种将多个数据源绑定在一块儿以使其同步的技术。微信
假设存在一个名为 username
的 <input>
。
<input type="text" id="username" />
假设你要使此输入的值与对象的属性保持同步。
const inputState = { id: 'username', value: '' }
当输入的值发生变化时,经过监听输入的变化事件,而后更新 inputState
的值,修改 inputState
是至关容易的。然而,反过来,在 inputState
被修改时更新输入,则至关困难。
ES6代理能够在这种状况下提供帮助。
const input = document.querySelector('#username') const handler = { set: function(target, key, value) { if (target.id && key === 'username') { target[key] = value; document.querySelector(`#${target.id}`) .value = value; return true } return false } } const proxy = new Proxy(inputState, handler) proxy.value = 'John Doe' console.log(proxy.value, input.value) // 双方都将印有“ John Doe”
这样,当 inputState
更改时,input
将反映已进行的更改。结合侦听 change
事件,这将生成 input
和 inputState
的简单双向数据绑定。
虽然这是一个有效的用例,但一般不建议这样作。之后再说。
缓存是一个古老的概念,它容许很是复杂和大型的应用程序保持相对的性能。缓存是存储某些数据的过程,以便在请求时能够更快地提供数据。缓存并不永久地存储任何数据。缓存失效是保证缓存新鲜的过程。这是开发人员共同的苦恼。正如Phil Karlton所说:"计算机科学中只有两件难事:缓存无效和给事物命名。"
ES6代理使缓存更加容易。例如,若是你要检查对象中是否存在某些东西,它将首先检查缓存并返回数据,或者若是不存在则进行其余操做以获取该数据。
假设你须要进行不少API调用才能获取特定信息并对其进行处理。
const getScoreboad = (player) => { fetch('some-api-url') .then((scoreboard) => { // 用记分牌作点什么 }) }
这就意味着,每当须要一个球员的记分牌时,就必须进行一次新的调用。相反,你能够在第一次请求时缓存记分牌,随后的请求能够从缓存中获取。
const cache = { 'John': ['55', '99'] } const handler = { get: function(target, player) { if(target[player] { return target[player] } else { fetch('some-api-url') .then(scoreboard => { target[player] = scoreboard return scoreboard }) } } } const proxy = new Proxy(cache, handler) // 访问缓存并使用记分牌作一些事情
这样,仅当缓存中不包含玩家的记分牌时,才会进行API调用。
最简单的用例是访问控制,ES6代理的大部份内容都属于访问控制。
让咱们探索使用E6代理的访问控制的一些实际应用。
ES6代理最直观的用例之一是验证对象内部的内容,以确保对象中的数据尽量准确。例如,若是你想强制执行产品描述的最大字符数,能够这样作。
const productDescs = {} const handler = { set: function(target, key, value) { if(value.length > 150) { value = value.substring(0, 150) } target[key] = value } } const proxy = new Proxy(productDescs, handler)
如今,即便你添加的描述超过150个字符,也会被删减并添加。
有时候你可能要确保不以任何方式修改对象,而且只能将其用于读取目的。 JavaScript提供了 Object.freeze()
来执行此操做,可是使用代理时,该行为更可自定义。
const importantData = { name: 'John Doe', age: 42 } const handler = { set: 'Read-Only', defineProperty: 'Read-Only', deleteProperty: 'Read-Only', preventExtensions: 'Read-Only', setPrototypeOf: 'Read-Only' } const proxy = new Proxy(importantData, handler)
如今,当你尝试以任何方式更改对象时,你只会收到一个字符串,表示只读。不然,你可能会引起错误以指示该对象是只读的。
JavaScript自己并无私有属性,除了闭包。当 Symbol
数据类型被引入时,它被用来模仿私有属性。但随着Object.getOwnPropertySymbols
方法的引入,它被抛弃了。ES6代理并非一个完美的解决方案,但在紧要关头它们能够完成任务。
一种常见的约定是经过在名称前加上下划线来标识私有属性,这个约定容许你使用ES6代理。
const object = { _privateProp: 42 } const handler = { has: function(target, key) { return !(key.startsWith('_') && key in target) }, get: function(target, key, receiver) { return key in receiver ? target[key] : undefined } } const proxy = new Proxy(object, handler) proxy._privateProp // undefined
添加 ownKeys
和 deleteProperty
会让这个实现更接近于真正的私有属性。而后,你仍然能够在开发者控制台中查看代理对象。若是你的用例与上面的实现一致,它仍然适用。
ES6代理并非性能密集型任务的理想选择。这就是为何进行必要的测试是相当重要的。代理能够在任何预期对象的地方使用,代理只需几行代码就能提供复杂的功能,这使它成为元编程的理想功能。
代理一般与另外一个称为Reflect的元编程功能一块儿使用。
来源:https://blog.logrocket.com,做者:Eslam Hefnawy Follow,翻译:公众号《前端全栈开发者》
本文首发于微信公众号《前端全栈开发者》,关注即送大礼包,准能为你节省很多钱!