JavaScript中如何通过反射调用非全局类实例的内部方法?
问题背景
从服务端获取数据时,会通过Worker发送数据包,包内指定请求返回后要调用的函数名称。Worker通过WebSocket将数据包发送给服务端,选用WebSocket是因为连接两端均可随时发起通信。当Worker收到服务端返回的数据包时,会调用包内指定名称的函数。
原有实现代码
基础功能示例:
function trial() { var p = new Packet(); var c1 = new Command("getGridColumnOptions"); p.addCommand(c1); p.addreturnRoutine("trial_return"); sendMessage(p); } function trial_return(packet) { console.log(JSON.stringify(packet)); }
此前Worker调用返回函数的逻辑:
window[packet.returnRoutine](packet);
现有问题
现在需要在类内部发起服务端调用,且要求将结果返回到该类的内部方法中,类代码如下:
class Tab { constructor() { } trial() { var p = new Packet(); var c1 = new Command("getGridColumnOptions"); p.addCommand(c1); p.addreturnRoutine("trial_return"); sendMessage(p); } trial_return(packet) { console.log(JSON.stringify(packet)); } }
Worker收到返回结果后无法调用已实例化类内部的返回方法,报错如下:
TypeError: window[json.returnRoutine] is not a function
根因是类方法没有绑定到全局window对象,且类实例本身也不会定义为全局变量,下面是3种可行的解决方案:
解决方案
方案1:新增全局回调注册表,支持绑定实例上下文
新增全局映射表存储回调的唯一标识、执行函数和绑定上下文,替换原有的直接从window取函数的逻辑,改动最小且兼容旧代码。
全局逻辑实现:// 全局只需要初始化一次 const callbackRegistry = new Map(); let callbackId = 0; // 注册回调方法,支持绑定上下文 function registerCallback(fn, ctx = null) { const id = `callback_${callbackId++}`; callbackRegistry.set(id, { fn, ctx }); return id; } // 替换原Worker侧的window[xxx]调用逻辑 function invokeCallback(packet) { const callbackInfo = callbackRegistry.get(packet.returnRoutine); if (callbackInfo) { callbackInfo.fn.call(callbackInfo.ctx, packet); // 单次请求回调调用完成后删除,避免内存泄漏 callbackRegistry.delete(packet.returnRoutine); return; } // 兜底兼容原有全局函数逻辑 if (typeof window[packet.returnRoutine] === 'function') { window[packet.returnRoutine](packet); } }类内部调用修改为:
class Tab { trial() { var p = new Packet(); var c1 = new Command("getGridColumnOptions"); p.addCommand(c1); // 注册回调并绑定当前类实例为上下文 const cbId = registerCallback(this.trial_return, this); p.addreturnRoutine(cbId); sendMessage(p); } trial_return(packet) { console.log(JSON.stringify(packet)); } }方案2:用Promise封装请求逻辑,规避回调注册
把请求过程封装为返回Promise的方法,不需要单独定义回调方法,更符合现代JS编码习惯:// 全局封装请求方法 function request(commands) { return new Promise((resolve) => { const p = new Packet(); commands.forEach(cmd => p.addCommand(cmd)); const cbId = registerCallback(resolve); p.addreturnRoutine(cbId); sendMessage(p); }) }类内部调用简化为:
class Tab { async trial() { const c1 = new Command("getGridColumnOptions"); const packet = await request([c1]); console.log(JSON.stringify(packet)); } }方案3:实例全局注册,适合多回调复用场景
如果类实例长期存在且需要调用多个回调方法,可以给每个实例生成唯一标识,提前注册到全局映射表,请求时传递实例ID:方法名作为回调标识:// 全局实例注册表 const instanceRegistry = new Map(); let instanceId = 0; // 类实现 class Tab { constructor() { // 生成唯一实例ID并注册 this.id = `tab_${instanceId++}`; instanceRegistry.set(this.id, this); } trial() { var p = new Packet(); var c1 = new Command("getGridColumnOptions"); p.addCommand(c1); p.addreturnRoutine(`${this.id}:trial_return`); sendMessage(p); } trial_return(packet) { console.log(JSON.stringify(packet)); } // 实例销毁时删除注册表记录,避免内存泄漏 destroy() { instanceRegistry.delete(this.id); } } // Worker侧调用逻辑 function invokeCallback(packet) { const routineStr = packet.returnRoutine; if (routineStr.includes(':')) { const [instanceId, methodName] = routineStr.split(':'); const instance = instanceRegistry.get(instanceId); instance?.[methodName]?.(packet); return; } // 兜底兼容原有逻辑 window[routineStr]?.(packet); }
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

