# 元编程构造简单优雅解决方案 ECMAScript 6中引入了相关 API —— `Proxy`[^1]。 搭配`Reflect`使用例子: ```js var loggedObj = new Proxy(obj, { set: function(target, name, value, receiver) { var success = Reflect.set(target,name, value, receiver); if (success) { log('property ' + name + ' on ' + target + ' set to ' + value); } return success; }, get(target, name) { console.log('get', target, name); return Reflect.get(target, name); }, deleteProperty(target, name) { console.log('delete' + name); return Reflect.deleteProperty(target, name); }, has(target, name) { console.log('has' + name); return Reflect.has(target, name); } }); ``` 下面用例个示例来讲解何为优雅的元编程解决方案。 ## API接口 SDK 封装 本章节以 ***腾讯云/QCloud*** 为例。 ### 云服务器 CVM[^2] 请求形式: ``` https://cvm.api.qcloud.com/v2/index.php? Action=DescribeInstances &SecretId=xxxxxxx &Region=gz &Timestamp=1465055529 &Nonce=59485 &Signature=mysignature &instanceIds.0=ins-0hm4gvho &instanceIds.1=ins-8oby8q00 &offset=0 &limit=20 &status=2 &zoneId=100003 ``` ### 传统解决方案 常规的解决方案大体如下: ```js class Cvm(){ constructor(config){ // 将配置信息传入进行初始化 this.hosts = 'https://cvm.api.qcloud.com/v2/index.php'; } describeInstances(options){ // 查看实例列表为例 return request(this.hosts,{ action: 'describeInstances', param1: options.param1 // 其他参数拼装 }).then(result=>{ return JSON.parse(result); }); } method2(options){ // 查看实例列表为例 return request(this.hosts,{ action: 'method2', param1: options.param1 // 其他参数拼装 }).then(result=>{ return JSON.parse(result); }); } } ``` 如果再有其他服务,再定义 class,再封装方法。 这类处理有一个特点就是,共性很多,都是发送 HTTP 请求,获取请求结果。差异化在于: 1. 域名,QCloud 的规律为 https://{服务名}.api.qcloud.com/v2/index.php 2. 方法名,即不同的 `Action` 3. 请求类型和参数 ### 简单优雅解决方案 ```js // 引入 HTTP 请求相关封装库 import request from './request'; // 列举 API 别名,包括 QCloud CVM、CDN、CDB 等所有服务 const SDKS = ['bm', 'cdn', 'cdb', 'cvm', 'cbs', 'csec', 'dayu', 'lb', 'monitor', 'scaling', 'sqlserver', 'redis', 'cmem', 'trade', 'tdsql', 'vpc', 'wenzhi', 'yunsou']; // 默认参数 const DEFAULTS = { SecretId: '', // Signature: '', Region: 'gz', Nonce: parseInt(Math.random() * 999999, 10), Timestamp: parseInt(new Date() / 1000, 10) }; const lazyLoad = service => (options) => { // 设置各服务的 api host const settings = { api: `https://${service}.api.qcloud.com/v2/index.php`, // 预留 actions: {} }; // 重要事情说三遍: // 核心代码开始 // 核心代码开始 // 核心代码开始 return new Proxy({}, { // 创建代理 // 定义 get 方法 // 例子: // const obj = new Obj(); // obj.prop 获取属性,用的 get 方法 // obj.prop() 获取方法,用的依然是 get 方法 get: (target, property) => (opts) => { // 非重要,将 Action 改为首字母大写,如 'ListRegions' const action = property.replace(property.charAt(0), property.charAt(0).toUpperCase()); // 拼装请求参数 let params = Object.assign({}, DEFAULTS, options); params = Object.assign({Action: property}, params, opts); // 预留了 GET/POST 自定义 params.method = settings.actions[action] || 'post'; // 发送请求,获取请求结果返回 // 由于不同接口均是 HTTP/S 方式请求,只需将对应方法封装完成,即可实现复用 return request(settings.api, params); } }); }; // LazyLoad 懒加载优化性能,但对于元编程来讲收益并不太大 SDKS.forEach((item) => { exports[item.toUpperCase()] = lazyLoad(item); }); ``` 使用示例: ```js import {TRADE} from 'wqcloud'; const trade = TRADE(options); // ES7 Async/Await 方式 (async() => { const userInfo = await trade.DescribeUserInfo(params); console.log(userInfo); })(); // ES5 Promise 方式 trade.DescribeUserInfo(params).then((userInfo) => { console.log(userInfo); }); ``` 完整项目源码: [^1]: 参考文档: [^2]: 文档说明: