如何实现可在其他函数内调用的函数及链式调用结构?
Hey there! Let's break down your two requirements step by step with practical, runnable examples that you can adapt for your code.
1. Writing Functions That Can Be Called Inside Another Function (Nested/Internal Functions)
Internal functions are perfect for encapsulating helper logic, avoiding global scope pollution, and leveraging closures to access variables from the outer function. Here are two common use cases:
Case 1: Helper Functions Called Directly Inside the Parent Function
Use this pattern when you need a dedicated utility function only relevant to the parent's logic:
function calculateOrderTotal(items, discountPercentage) { // Internal helper: calculates discounted price for a single item function applyDiscount(price, discount) { return price * (1 - discount / 100); } let total = 0; items.forEach(item => { total += applyDiscount(item.price, discountPercentage); }); return total; } // Usage example const cart = [{name: "Wireless Headphones", price: 200}, {name: "Charger", price: 30}]; console.log(calculateOrderTotal(cart, 15)); // Output: 195.5
Note: applyDiscount is only accessible inside calculateOrderTotal—it’s hidden from the global scope, keeping your code clean.
Case 2: Returning Internal Functions (Leveraging Closures)
This pattern lets you create functions that retain access to the parent function’s variables even after the parent has finished executing:
function createUserGreeting(username) { // Internal function that remembers the username return function(greetingMessage) { return `${greetingMessage}, ${username}!`; }; } // Usage example const greetAlice = createUserGreeting("Alice"); console.log(greetAlice("Good morning")); // Output: "Good morning, Alice!" console.log(greetAlice("Welcome back")); // Output: "Welcome back, Alice!"
Here, the returned internal function maintains access to the username variable long after createUserGreeting has run—that’s the power of closures.
2. Building a Chainable Call Structure
Chainable calls work by having each method return the current object (or a related object) after execution, allowing you to chain method calls one after another. Let’s build the exact structure you mentioned: require("Promotions").getDisounts().getProductDiscounts().getLength();
Option 1: Simple Object Literal (Best for Lightweight Modules)
// Promotions.js module.exports = { // Step 1: Fetch all discounts getDisounts() { // Simulate fetching discount data this.allDiscounts = [ {type: "product", value: 10}, {type: "category", value: 15}, {type: "product", value: 5}, {type: "product", value: 20} ]; return this; // Return the current object to enable chaining }, // Step 2: Filter to only product-specific discounts getProductDiscounts() { if (!this.allDiscounts) throw new Error("Call getDisounts() first!"); this.productDiscounts = this.allDiscounts.filter(d => d.type === "product"); return this; // Return the current object again }, // Step 3: Get the count of product discounts getLength() { if (!this.productDiscounts) throw new Error("Call getProductDiscounts() first!"); return this.productDiscounts.length; } };
Usage:
const promotions = require("./Promotions"); const productDiscountCount = promotions.getDisounts().getProductDiscounts().getLength(); console.log(productDiscountCount); // Output: 3
Option 2: Class-Based Structure (Best for Complex, Reusable Instances)
If you need multiple independent instances of your Promotions module, use a class:
// Promotions.js class Promotions { getDisounts() { this.allDiscounts = [ {type: "product", value: 10}, {type: "category", value: 15}, {type: "product", value: 5} ]; return this; } getProductDiscounts() { if (!this.allDiscounts) throw new Error("Call getDisounts() first!"); this.productDiscounts = this.allDiscounts.filter(d => d.type === "product"); return this; } getLength() { if (!this.productDiscounts) throw new Error("Call getProductDiscounts() first!"); return this.productDiscounts.length; } } // Export a singleton instance (or export the class to let users instantiate their own) module.exports = new Promotions();
The usage remains identical to the object literal version.
内容的提问来源于stack exchange,提问作者Maxim Kuželj

