如何在JavaScript暴露模块模式中实现函数链式调用?
实现链式调用的解决方案
要实现moduleAda.message.send("hello").display.show()这样的链式调用,我们需要解决两个核心问题:
- 让
message.send()返回的对象包含display模块的引用,这样才能链式调用其方法。 - 处理
send()内部的异步操作(queryAda是Promise),确保display.show()能拿到异步请求完成后的结果。
下面是具体的重构步骤和代码:
1. 调整模块内部结构,添加状态存储
首先在暴露模块模式的内部添加一个状态变量,用来暂存send()的输入和异步返回的结果,这样display.show()可以直接获取这些数据:
var moduleAda = (function() { // 内部状态:存储当前请求的输入和处理后的输出 var currentRequest = { input: null, output: null }; // 原有的message和display对象放在这里,后续修改 })();
2. 抽离公共逻辑,避免代码重复
把显示和隐藏消息的逻辑抽成内部函数,让链式调用的方法和独立调用的方法都能复用:
// 内部复用的显示逻辑 function showMessage(input, output) { $("#ADA-Q-TXT").text(input); $("ADA-BUBBLE").css({ "margin-bottom": "112px" }); $("ADA-BUBBLE").html(output); styleTable(); setTimeout(function() { $("ADA-BUBBLE").addClass("SHOW"); }, 500); } // 内部复用的隐藏逻辑 function hideMessage() { $("ADA-BUBBLE").removeClass("SHOW"); $("ADA-EXAMPLES li").removeClass("SHOW"); setTimeout(function() { $("ADA-EXAMPLES").css({ display: "none" }); }, 500); }
3. 重构message.send()方法
修改send()方法,让它返回一个包含display的可链式调用对象,同时封装异步请求逻辑,确保结果能被正确存储:
var message = { send: function send(query) { // 保存当前请求的输入 currentRequest.input = query; // 显示加载 spinner $("spinner").css({ display: "block" }); // 封装异步请求为Promise(假设queryAda返回jQuery Deferred,本身兼容Promise) var requestPromise = queryAda(query) .done(function(result) { console.log("success", result); var processedMessage = cleanAda(result); console.log(processedMessage); storage.save(); // 保存成功后的处理结果 currentRequest.output = processedMessage; }) .fail(function(xhr) { console.log("error", xhr); var errorMessage = "I'm sorry, but there was an error and I couldn't retrieve your request. Please try again."; // 保存错误提示信息 currentRequest.output = errorMessage; }) .always(function() { // 请求完成后隐藏 spinner $("spinner").css({ display: "none" }); }); // 创建可链式调用的对象:融合Promise和display方法 var chainableObj = Object.assign({}, requestPromise, { display: { // 重写show方法,确保等待异步请求完成后执行 show: function() { requestPromise.then(function() { showMessage(currentRequest.input, currentRequest.output); }); // 返回自身,支持继续链式调用 return chainableObj; }, // 复用隐藏逻辑,同时支持链式 remove: function() { hideMessage(); return chainableObj; } } }); return chainableObj; } };
4. 保留原display模块的独立调用能力
为了不破坏原有的moduleAda.display.show()或moduleAda.display.remove()调用,我们基于抽离的公共逻辑重新定义display对象:
// 原有的display模块,保留独立调用能力 var display = { remove: function() { hideMessage(); // 支持独立链式调用,比如moduleAda.display.remove().show(...) return this; }, show: function(input, output) { showMessage(input, output); return this; } };
5. 完整的模块代码
把所有部分整合起来,最终的moduleAda代码如下:
var moduleAda = (function() { // 内部状态:存储当前请求的输入和处理后的输出 var currentRequest = { input: null, output: null }; // 内部复用的显示逻辑 function showMessage(input, output) { $("#ADA-Q-TXT").text(input); $("ADA-BUBBLE").css({ "margin-bottom": "112px" }); $("ADA-BUBBLE").html(output); styleTable(); setTimeout(function() { $("ADA-BUBBLE").addClass("SHOW"); }, 500); } // 内部复用的隐藏逻辑 function hideMessage() { $("ADA-BUBBLE").removeClass("SHOW"); $("ADA-EXAMPLES li").removeClass("SHOW"); setTimeout(function() { $("ADA-EXAMPLES").css({ display: "none" }); }, 500); } var message = { send: function send(query) { currentRequest.input = query; $("spinner").css({ display: "block" }); var requestPromise = queryAda(query) .done(function(result) { console.log("success", result); var processedMessage = cleanAda(result); console.log(processedMessage); storage.save(); currentRequest.output = processedMessage; }) .fail(function(xhr) { console.log("error", xhr); var errorMessage = "I'm sorry, but there was an error and I couldn't retrieve your request. Please try again."; currentRequest.output = errorMessage; }) .always(function() { $("spinner").css({ display: "none" }); }); var chainableObj = Object.assign({}, requestPromise, { display: { show: function() { requestPromise.then(function() { showMessage(currentRequest.input, currentRequest.output); }); return chainableObj; }, remove: function() { hideMessage(); return chainableObj; } } }); return chainableObj; } }; var display = { remove: function() { hideMessage(); return this; }, show: function(input, output) { showMessage(input, output); return this; } }; return { message: message, display: display }; })();
6. 链式调用的使用方式
现在你可以按照期望的方式进行链式调用了:
// 基础链式调用 moduleAda.message.send("hello").display.show(); // 连续链式调用(比如发送请求后显示,再隐藏) moduleAda.message.send("hello").display.show().display.remove();
同时,原有的独立调用方式依然有效:
// 独立显示消息 moduleAda.display.show("custom input", "custom output"); // 独立隐藏消息 moduleAda.display.remove();
关键说明
- 异步处理:通过将
queryAda的请求封装为Promise,在display.show()中使用then()确保只有当异步请求完成后才执行显示逻辑,避免数据未准备好的问题。 - 链式核心:每个方法返回包含后续可调用方法的对象(这里是
chainableObj),这样就能持续链式调用。 - 代码复用:抽离公共的显示/隐藏逻辑,避免重复代码,同时保证模块的灵活性。
内容的提问来源于stack exchange,提问作者Jon Hart
相关产品推荐
相关产品推荐

