ES6代理和反射实现代理方法代理

个人网站:aijianli.site/ 可以免费在线制作简历,提供PDF下载,方便快捷。

vqa (view query api) 是一个后端查询组件,方便写 sql 查询,能减少在后端代码中增加业务代码。前端调用接口的方式比较复杂。因此使用ES6的反射和代理对调用方式进行改造,实现方便调用。

一、原始的vqa接口调用方式

js 复制代码
function findStatsTable() {
    let params = {
        method: "stats-service-stats-table.findTable",
        datas: {
            st_id: "1234567890",
        }
    };
    this.axios.post("/stats/vqa-query", params).then(res => {
        console.log(res)
    })
}

存在的问题:

  • 参数params太复杂,并且同一个模块有很多vqa,它们的 params.method 中的前缀(stats-service-stats-table)是相同的,只有方法名不同,每次查询都要复制相同的内容,后期如有改变很难维护
  • 同一个服务的接口地址(/stats/vqa-query)相同,每次都要写相同的字符串,太多重复。

二、改进的vqa调用方式

js 复制代码
function findStatsTable() {
    let params = {
         st_id: "1234567890",
    };
    this.$statsTable.findTable(params).then(res => {
        console.log(res)
    })
}

改进后的优点:

  • 调用简单,只需要关注请求参数、
  • 易维护,不会重复在不同的地方出现stats-service-stats-table,/stats/vqa-query等字符串

三、实现代码

3.1 类图结构设计

3.2 具体实现

其中,ParentInterface中的 _sendVqaRequest 方法内容如下,这里就是封装的原始的vqa的方法

js 复制代码
class ParentInterface {
    /*......*/

    /**
     * 项vqa接口发送请求, 注意vqa的请求都是 post 请求
     * @param {Object} params 对应查询的 method 的 datas 中需要的参数
     * @param {String} method 查询的 vqa 方法,不需要方法前缀,只需要方法名
     * @returns 
     */
    _sendVqaRequest(params, method) {
        params = this._getParams(params, method);
        return this[axiosSym].post(this[vqaUrlSym], params).then(res => {
            return res.data.content;
        })
    }
     /*......*/
}

为了实现调用类中不存在的方法时都统一调用 _sendVqaRequest 方法,需要使用到ES6的反射和代理,这才是实现此功能的关键。具体实现如下:

js 复制代码
	/**
 * 初始化所有接口
 * @param {*} app Vue
 * @param {*} axios 请求实例
 */
function initInterfaces(app, axios) {

    const statsTable = createInterfaceProxy(new StatsTable(axios));

    const interfaces = {
        statsTable
    };

    for (let interfac in interfaces) {
        app.prototype[`$${interfac}`] = interfaces[interfac];
    }
}

核心代码

js 复制代码
/**
 * 创建接口代理,处理vqa的查询,调用vqa方式时,获取调用方法的名称调用后台vqa
 * @param {Object} target 被代理的对象,这里是接口的实例
 * @returns 返回一个代理对象
 */
function createInterfaceProxy(target) {
    let hander = {
        get(target, key) {
            let vqaMethod = Reflect.get(target, key);
            if (vqaMethod) {
                return vqaMethod;
            }
            vqaMethod = Reflect.get(target, "_sendVqaRequest"); //写死,所有vqa的查询调用的都是 _sendVqaRequest 方法
            let vqaProxy = new Proxy(vqaMethod, {
                apply(funtarget, funthis, args) {
                    let params = [...args, key]
                    return Reflect.apply(funtarget, funthis, params)
                }
            })
            return vqaProxy;
        }
    }
    return new Proxy(target, hander)
}

如下代码,通过ES6的代理和反射,实现对target对象的代理。

通过代理target的get方法,当调用target调用自身方法时,如果调用的方法在target中原本就存在,则直接返回方法。若不存在,则返回一个代理对象,这个代理对象代理的是target中的一个公用的方法。由此实现当调用一个对象中不存在的方法时,都会统一调用到一个特定的公用方法。

3.3 部分代码展示

3.3.1 ParentInterface类

js 复制代码
/**
 * 所有接口类的父类,提供所有接口公用的方法
 * 注意:每个继承此父接口的具体类必须提在config文件中config对象中添加如下配置
 * 
 * 类型: {
 *  interfacePrefix: xxx, //后台接口的前缀
 * }
 * 
 * 例如 快照的 接口类配置如下
 * StatsSnapshot: {
        interfacePrefix: "/stats/snapshots",
    }
 * 
 * @author hebing
 * @date 2021/03/09
 */
import config from "./config.js";

//使用symbol来作为key,避免外部使用时更改这些属性,导致后续的请求出错
let axiosSym = Symbol("axios");
let vqaUrlSym = Symbol("vqaUrl");
let vqaPrefixSym = Symbol("vqaPrefix");
let interfacePrefixSym = Symbol("interfacePrefix");

class ParentInterface {

    constructor(className, axios) {
        //实现类的类名,根据创建的类的实例对象来从配置文件中取出具体的类的 vqaUrl, vqaPrefix 和 interfacePrefix
        this[axiosSym] = axios;
        let implClass = className;

        let configs = config[implClass];

        if (!configs) throw new Error(`接口类【${className}】缺少配置文件!`)

        this[vqaUrlSym] = configs.vqaUrl;
        this[vqaPrefixSym] = configs.vqaPrefix;
        this[interfacePrefixSym] = configs.interfacePrefix;

    }
    /**
     * 修正查询vqa的参数
     * @param {Object} params 查询vqa所需要的参数
     */
    _fixParams(params) {
        params.method = this[vqaPrefixSym] + params.method;
    }

    /**
     * 获取请求参数
     * @param {Object} datas 请求参数
     * @param {String} method 请求方法
     * @returns 返回查询vqa需要的参数
     */
    _getParams(datas, method) {
        let params = {
            method: method,
            datas: datas
        }
        this._fixParams(params);
        return params;
    }

    /**
     * 项vqa接口发送请求, 注意vqa的请求都是 post 请求
     * @param {Object} params 对应查询的 method 的 datas 中需要的参数
     * @param {String} method 查询的 vqa 方法,不需要方法前缀,只需要方法名
     * @returns 
     */
    _sendVqaRequest(params, method) {
        params = this._getParams(params, method);
        return this[axiosSym].post(this[vqaUrlSym], params).then(res => {
            return res.data.content;
        })
    }

    /**
     * 修正请求路径, 后台接口时使用
     * @param {*} method 除config配置文件中的前缀后的部分
     * @param {*} fullUrl 指定 method 是否是全路径
     * @returns 
     */
    _fixUrl(method, fullUrl) {
        let realUrl = this[interfacePrefixSym];
        if (method) {
            if (fullUrl) {
                realUrl = method;
            } else if (method.startsWith("/")) {
                realUrl += method;
            } else {
                realUrl += ("/" + method);
            }
        }
        return realUrl;
    }

    /**
     * 向后台接口发送请求
     * @param {Object} params 对应后台接口的参数
     * @param {String} method 除config配置文件中的前缀后的部分
     * @param {Boolean} fullUrl 指定 method 是否是全路径
     * @returns 
     */
    _sendPostRequest(params, method, fullUrl) {
        let realUrl = this._fixUrl(method, fullUrl);
        return this[axiosSym].post(realUrl, params, {
            loading: params.loading
        }).then(res => {
            return res.data.content;
        })
    }

    /**
     * 向后台接口发送请求
     * @param {Object} params 对应后台接口的参数
     * @param {String} method 除config配置文件中的前缀后的部分
     * @param {Boolean} fullUrl 指定 method 是否是全路径
     * @returns 
     */
    _sendGetRequest(params, method, fullUrl) {
        let realUrl = this._fixUrl(method, fullUrl);
        return this[axiosSym].get(realUrl, {
            params
        }).then(res => {
            return res.data.content;
        })
    }

   
}

export default ParentInterface;

3.3.2 index.js

js 复制代码
import StatsTable from "./stats/StatsTable.js";

/**
 * 创建接口代理,处理vqa的查询,调用vqa方式时,获取调用方法的名称调用后台vqa
 * @param {Object} target 被代理的对象,这里是接口的实例
 * @returns 返回一个代理对象
 */
function createInterfaceProxy(target) {
    let hander = {
        get(target, key) {
            let vqaMethod = Reflect.get(target, key);
            if (vqaMethod) {
                return vqaMethod;
            }
            //写死,所有vqa的查询调用的都是 _sendVqaRequest 方法
            vqaMethod = Reflect.get(target, "_sendVqaRequest"); 
            let vqaProxy = new Proxy(vqaMethod, {
                apply(funtarget, funthis, args) {
                    let params = [...args, key]
                    return Reflect.apply(funtarget, funthis, params)
                }
            })
            return vqaProxy;
        }
    }
    return new Proxy(target, hander)
}

/**
 * 初始化所有接口
 * @param {*} app Vue
 * @param {*} axios 请求实例
 */
function initInterfaces(app, axios) {

    const statsTable = createInterfaceProxy(new StatsTable(axios));

    const interfaces = {
        statsTable
    };

    for (let interfac in interfaces) {
        app.prototype[`$${interfac}`] = interfaces[interfac];
    }
}

export default initInterfaces;
相关推荐
一颗花生米。21 分钟前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐0125 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199526 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&1 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
Jiaberrr7 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
安冬的码畜日常9 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ9 小时前
html+css+js实现step进度条效果
javascript·css·html