如何在Conv类外部定义可访问payload的转换函数?
让Conv转换函数访问完整payload(不修改原类)
需求背景
我有一个Conv类(代码见文末),通过run方法实现对象转换,实际版本包含runInverse方法(示例已省略)。
当前使用方式:
function fn(s: String, inv: Boolean) { return inv ? "inversed" : s; } let conv = new Conv(); conv.fromPathToWithFn("data.id", fn, "internal_id"); conv.run({ data: { id: "1234" }}); // => { internal_id: "1234" }
希望在不修改Conv类本身的前提下,让转换函数fn能访问run/runInverse处理的完整payload对象,例如:
function fn(s: String, inv: Boolean) { if (this.payload.otherValue) { return "nice"; } else { return s; } }
调用效果:
conv.run({ data: { id: "1234" }, otherValue: "defined" }); // => { internal_id: "nice" }
解决方案
核心思路是在转换函数执行时,将完整payload注入到函数的执行上下文中,同时避免修改原Conv类代码。以下是两种可行方案:
方案1:通过闭包绑定上下文
利用闭包在run执行时传递payload,同时包装转换函数:
// 包装转换函数,使其能访问conv实例的payload function createBoundConverter(conv, originalFn) { return function(s, inv) { // 将this指向conv实例,同时conv上已挂载payload return originalFn.call(conv, s, inv); }; } // 扩展Conv的run方法,挂载payload到实例 const originalRun = Conv.prototype.run; Conv.prototype.run = function(payload, parentString = "") { this.payload = payload; // 挂载完整payload const result = originalRun.call(this, payload, parentString); delete this.payload; // 执行后清理,避免污染实例 return result; }; // 使用示例 let conv = new Conv(); const boundFn = createBoundConverter(conv, function(s, inv) { if (this.payload.otherValue) { return "nice"; } else { return s; } }); conv.fromPathToWithFn("data.id", boundFn, "internal_id"); conv.run({ data: { id: "1234" }, otherValue: "defined" }); // 输出 { internal_id: "nice" }
方案2:动态包装转换函数
在run执行时动态替换转换函数,注入payload上下文:
// 扩展fromPathToWithFn,标记需要绑定上下文的函数 const originalFromFn = Conv.prototype.fromPathToWithFn; Conv.prototype.fromPathToWithFn = function(from, fn, to) { originalFromFn.call(this, from, { isBound: true, fn }, to); }; // 重写run方法,动态包装转换函数 const originalRun = Conv.prototype.run; Conv.prototype.run = function(payload, parentString = "") { // 临时包装转换函数,注入payload上下文 this.converted = this.converted.map(item => { if (item.fn?.isBound) { return { ...item, fn: (s, inv) => item.fn.fn.call({ payload }, s, inv) }; } return item; }); const result = originalRun.call(this, payload, parentString); // 恢复原始转换函数,避免影响后续调用 this.converted = this.converted.map(item => { if (item.fn?.isBound) { return { ...item, fn: item.fn.fn }; } return item; }); return result; }; // 使用示例 let conv = new Conv(); function fn(s, inv) { if (this.payload.otherValue) { return "nice"; } else { return s; } } conv.fromPathToWithFn("data.id", fn, "internal_id"); conv.run({ data: { id: "1234" }, otherValue: "defined" }); // 输出 { internal_id: "nice" }
原Conv类代码
export class Conv { converted: Array<any> = []; // 转换规则集合 inverse: Boolean = false; // 是否启用反向转换 newPayload: object = {}; // 转换结果对象 constructor() { } /** * 将值从源路径复制到目标路径 * @param {String} from 源路径 * @param {String} to 目标路径 */ fromPathTo(from: String, to: String) { this.converted.push({ from, to }); } /** * 读取源路径值,经转换函数处理后存入目标路径 * @param {String} from 源路径 * @param {Function} fn 转换函数 * @param {String} to 目标路径 */ fromPathToWithFn(from: String, fn: Function, to: String) { this.converted.push({ from, fn, to }); } /** * 执行对象转换 * @param {Object} payload 待转换对象 * @returns {Object} 转换后的对象 */ run(payload: Object, parentString: String = "") { this.inverse = false; if (typeof payload !== "object" || payload === null || Array.isArray(payload)) { return payload; } for (let prop in payload) { let currentPath: string = prop; if (parentString !== "") { currentPath = parentString + "." + prop; } if (typeof payload[prop] === "object" && !Array.isArray(payload[prop])) { this.run(payload[prop], currentPath); } else { const newProp: string = this.converted.find(_ => _.from === currentPath)?.to; const func: Function | undefined = this.converted.find(_ => _.from === currentPath)?.fn; const def: String | null = this.converted.find(_ => _.from === currentPath)?.def; if (newProp === undefined || newProp === null) { break; } if (newProp && newProp !== prop) { typeof func !== "undefined" ? lodash.set( this.newPayload, newProp, func(this.run(lodash.get(payload, prop)), this.inverse) ) : this.run(lodash.get(payload, prop)) === null || typeof this.run(lodash.get(payload, prop)) === "undefined" ? lodash.set(this.newPayload, newProp, def) : lodash.set(this.newPayload, newProp, this.run(lodash.get(payload, prop))); } else { typeof func !== "undefined" ? lodash.set( this.newPayload, prop, func(this.run(lodash.get(payload, prop)), this.inverse) ) : this.run(lodash.get(this.newPayload, currentPath)) === null || typeof this.run(lodash.get(this.newPayload, prop)) === "undefined" ? lodash.set(this.newPayload, currentPath, def) : lodash.set(this.newPayload, currentPath, this.run(lodash.get(payload, prop))); } } } return this.newPayload; } }
内容的提问来源于stack exchange,提问作者Solon
相关产品推荐
相关产品推荐

