这就是我?代理好像在ES6的其他特性的大肆宣传下迷失了自我。 但我对ES6的代理还是非常激动的,因为它们是一个用来调节访问JS应用程序里面对象的简洁语义化的构造。 在这篇文章里,我会尽我所能来解释它们是如何工作的,并且列举出几种你可能会用到的实用的方法。 |
什么是代理?
|
ES6 的代理
|
handler 是一个对象,包含了你想要拦截和处理的操作。它被作为 Proxy 构造器的第二个参数传入。它实现了 Proxy API (例如:get, set, apply 等等)。 trap 是指代 handler 中处理特定方法的一个方法项。因此如果你要拦截对 get 方法的调用,就要定义一个 get 的 trap,诸如此类。 最后一个东西就是: 你还应该对 Reflect API 有所了解, 它也能在整个全局对象上被访问到。这个我会推迟到 MDN 来讲述它是什么,因为它所解释的 Reflect 是简明扼要的,也因为它就是你一看就明白的那种解释之一。相信我。 基本的使用在我深入讲述哪些我见过的有趣的代理示例之前,先来一个"hello world“的示例。更多有关于使用代理的一步一步的介绍,你可以看看 Nicholas Zakas《理解ES6》的相关章节,在线可以免费阅读。
|
ES6 Proxy 的使用场景
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
let numericDataStore = { count: 0, amount: 1234, total: 14 }; numericDataStore = new Proxy(numericDataStore, { set(target, key, value, proxy) { if ( typeof value !== 'number' ) { throw Error( "Properties in numericDataStore can only be numbers" ); } return Reflect.set(target, key, value, proxy); } }); // this will throw an error numericDataStore.count = "foo" ; // this will set the new value as expected numericDataStore.count = 333; |
这很有趣,但是你会有多频繁的使用类型完全相同的属性来创建对象呢?
如果你想要为一个对象上的某些或者所有属性编写自定义的验证器, 代码就会稍稍变得复杂起来,不过我所喜爱的是代理如何来帮助你将验证的代码从更加核心的逻辑分离。我是那个唯一讨厌将验证代码跟方法和类混在一起的人吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// Define a validator that takes custom validators and returns a proxy function createValidator(target, validator) { return new Proxy(target, { _validator: validator, set(target, key, value, proxy) { if (target.hasOwnProperty(key)) { let validator = this ._validator[key]; if (!!validator(value)) { return Reflect.set(target, key, value, proxy); } else { throw Error(`Cannot set ${key} to ${value}. Invalid.`); } } else { // prevent setting a property that isn't explicitly defined in the validator throw Error(`${key} is not a valid property`) } } }); } // Now, just define validators for each property const personValidators = { name(val) { return typeof val === 'string '; }, age(val) { return typeof age === ' number ' && age > 18; } } class Person { constructor(name, age) { this.name = name; this.age = age; return createValidator(this, personValidators); } } const bill = new Person(' Bill ', 25); // all of these throw an error bill.name = 0; bill.age = ' Bill'; bill.age = 15; |
这样,你就可以无限扩展你的验证代码,而无须修改你的类/方法。
还有一个跟验证相关的点子。假如说你想要检查将要被传入一个特定方法的值,并且当其实现不正确时记录一些有帮助的警告信息。你可以用代理来做这件事情,而且保持类型检查的代码不会碍什么事。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
let obj = { pickyMethodOne: function (obj, str, num) { /* ... */ }, pickyMethodTwo: function (num, obj) { /*... */ } }; const argTypes = { pickyMethodOne: [ "object" , "string" , "number" ], pickyMethodTwo: [ "number" , "object" ] }; obj = new Proxy(obj, { get: function (target, key, proxy) { var value = target[key]; return function (...args) { var checkArgs = argChecker(key, args, argTypes[key]); return Reflect.apply(value, target, args); }; } }); function argChecker(name, args, checkers) { for ( var idx = 0; idx < args.length; idx++) { var arg = args[idx]; var type = checkers[idx]; if (!arg || typeof arg !== type) { console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`); } } } obj.pickyMethodOne(); // > You are incorrectly implementing the signature of pickyMethodOne. Check param 1 // > You are incorrectly implementing the signature of pickyMethodOne. Check param 2 // > You are incorrectly implementing the signature of pickyMethodOne. Check param 3 obj.pickyMethodTwo( "wopdopadoo" , {}); // > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1 // No warnings logged obj.pickyMethodOne({}, "a little string" , 123); obj.pickyMethodOne(123, {}); |
2. 在 JavaScript中实现真正的私有
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var api = { _apiKey: '123abc456def' , /* mock methods that use this._apiKey */ getUsers: function (){}, getUser: function (userId){}, setUser: function (userId, config){} }; // logs '123abc456def'; console.log( "An apiKey we want to keep private" , api._apiKey); // get and mutate _apiKeys as desired var apiKey = api._apiKey; api._apiKey = '987654321' ; |
有了 ES6 的代理,就能在JavaScript中实现真正完全的私有,有两种方式。
首先,你可以使用一个代理来拦截对于特定属性的请求,然后对其进行限制或者就只是返回undefined。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
var api = { _apiKey: '123abc456def' , /* mock methods that use this._apiKey */ getUsers: function (){ }, getUser: function (userId){ }, setUser: function (userId, config){ } }; // Add other restricted properties to this array const RESTRICTED = [ '_apiKey' ]; api = new Proxy(api, { get(target, key, proxy) { if (RESTRICTED.indexOf(key) > -1) { throw Error(`${key} is restricted. Please see api documentation for further info.`); } return Reflect.get(target, key, proxy); }, set(target, key, value, proxy) { if (RESTRICTED.indexOf(key) > -1) { throw Error(`${key} is restricted. Please see api documentation for further info.`); } return Reflect.get(target, key, value, proxy); } }); // throws an error console.log(api._apiKey); // throws an error api._apiKey = '987654321' ; |
你也可以使用 has 这个 trap 来掩盖这个属性存在的事实。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
var api = { _apiKey: '123abc456def' , /* mock methods that use this._apiKey */ getUsers: function (){ }, getUser: function (userId){ }, setUser: function (userId, config){ } }; // Add other restricted properties to this array const RESTRICTED = [ '_apiKey' ]; api = new Proxy(api, { has(target, key) { return (RESTRICTED.indexOf(key) > -1) ? false : Reflect.has(target, key); } }); // these log false, and `for in` iterators will ignore _apiKey console.log( "_apiKey" in api); for ( var key in api) { if (api.hasOwnProperty(key) && key === "_apiKey" ) { console.log( "This will never be logged because the proxy obscures _apiKey..." ) } } |
3. 静默地对象访问日志
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
let api = { _apiKey: '123abc456def' , getUsers: function () { /* ... */ }, getUser: function (userId) { /* ... */ }, setUser: function (userId, config) { /* ... */ } }; api = new Proxy(api, { get: function (target, key, proxy) { var value = target[key]; return function (...arguments) { logMethodAsync( new Date(), key); return Reflect.apply(value, target, arguments); }; } }); // executes apply trap in the background api.getUsers(); function logMethodAsync(timestamp, method) { setTimeout( function () { console.log(`${timestamp} - Logging ${method} request asynchronously.`); }, 0) } |
这很酷,因为你可以在不搞乱应用程序代码或者阻碍其执行的前提下对任何类型的东西进行日志记录。要跟踪特定方法随着时间的性能表现应该不会要太多的代码。
4. 提供警告信息或者阻止特定操作的执行
假设你想要阻止任何人删除属性 noDelete, 想要让那些调用 oldMethod 的用户知道它已经被弃用了, 还想要阻止任何人修改 doNotChange 属性。下面就是一种快速的实现办法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
let dataStore = { noDelete: 1235, oldMethod: function () { /*...*/ }, doNotChange: "tried and true" }; const NODELETE = [ 'noDelete' ]; const DEPRECATED = [ 'oldMethod' ]; const NOCHANGE = [ 'doNotChange' ]; dataStore = new Proxy(dataStore, { set(target, key, value, proxy) { if (NOCHANGE.includes(key)) { throw Error(`Error! ${key} is immutable.`); } return Reflect.set(target, key, value, proxy); }, deleteProperty(target, key) { if (NODELETE.includes(key)) { throw Error(`Error! ${key} cannot be deleted.`); } return Reflect.deleteProperty(target, key); }, get(target, key, proxy) { if (DEPRECATED.includes(key)) { console.warn(`Warning! ${key} is deprecated.`); } var val = target[key]; return typeof val === 'function' ? function (...args) { Reflect.apply(target[key], target, args); } : val; } }); // these will throw errors or log warnings, respectively dataStore.doNotChange = "foo" ; delete dataStore.noDelete; dataStore.oldMethod(); |
5. 阻止非必要的重度资源消耗型操作
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
let obj = { getGiantFile: function (fileId) { /*...*/ } }; obj = new Proxy(obj, { get(target, key, proxy) { return function (...args) { const id = args[0]; let isEnroute = checkEnroute(id); let isDownloading = checkStatus(id); let cached = getCached(id); if (isEnroute || isDownloading) { return false ; } if (cached) { return cached; } return Reflect.apply(target[key], target, args); } } }); |
6. 即时撤销对敏感数据的访问
代理支持在任何时候撤销对目标对象的访问。这可能在你想要完全封锁对某些数据和API的访问,这样的场景下会有用(例如安全、认证和性能方面的因素) 。这里有一个基础的示例,使用了 revocable 方法。注意当你在使用它时,并没有调用Proxy上的 new 关键词。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
let sensitiveData = { username: 'devbryce' }; const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler); function handleSuspectedHack(){ // Don't panic // Breathe revokeAccess(); } // logs 'devbryce' console.log(sensitiveData.username); handleSuspectedHack(); // TypeError: Revoked console.log(sensitiveData.username); |
好了,我知道的就这些。我也乐于听到你已经在自己的工作中用到代理时使用的方法。
祝你编写JavaScript愉快,若能在本文中发现某些我需要改正的东西,我也会很高兴!
本文转自:开源中国社区 [http://www.oschina.net]
本文标题:6 个 ES6 代理使用案例
本文地址:http://www.oschina.net/translate/use-cases-for-es6-proxies
参与翻译:leoxu, tv_哇, 无若
英文原文:6 compelling use cases for ES6 proxies