JavaScript函数间变量共享及重复功能代码简化方案咨询
Great question! Let's break this down clearly—you don't need to jump straight to classes to fix this repetition, and there are several clean, flexible ways to simplify your code and share variables between functions.
Do You Need Classes? Nope—Here Are Better Alternatives
Your two functions have tons of repeated code (DOM selections, Stripe setup, font/style config) — classes work, but composition (building functionality from shared pieces) is often lighter and more flexible for this kind of scenario. Let’s start with the simplest fixes:
1. Extract Shared Logic into Reusable Functions
First, pull out all the repeated code into standalone utility functions. This way, you only write it once, and both CreditCard and DirectDebit can reuse it.
// Get all shared DOM/configuration values in one place function getSharedPaymentSettings() { const stripeKeyTag = document.querySelector('meta[name=stripe-publishable-key]'); const serverConstants = document.querySelector('#server_side_constants'); // Exit early if critical elements are missing if (!stripeKeyTag || !serverConstants) return null; return { stripeKey: stripeKeyTag.getAttribute('content'), fontName: serverConstants.getAttribute('data-font-name'), fontColor: serverConstants.getAttribute('data-font-color'), fontSource: serverConstants.getAttribute('data-font-source'), locale: document.querySelector('html').getAttribute('lang'), errorElement: document.querySelector('#stripe_error_message'), clientSecret: document.querySelector('#client_secret'), submitButton: document.querySelector('input[type=submit]'), spinner: document.querySelector('#spinner'), nameField: document.querySelector('#name'), emailField: document.querySelector('#email') }; } // Initialize Stripe and elements with shared config function initSharedStripe(settings) { if (!settings) return null; const stripe = Stripe(settings.stripeKey); const elements = stripe.elements({ fonts: [{ family: settings.fontName, src: settings.fontSource }], locale: settings.locale }); const baseStyle = { base: { fontFamily: settings.fontName, color: settings.fontColor } }; return { stripe, elements, baseStyle }; }
Now refactor your original functions to use these utilities:
function CreditCard() { const sharedSettings = getSharedPaymentSettings(); if (!sharedSettings) return; const { stripe, elements, baseStyle } = initSharedStripe(sharedSettings); const form = document.querySelector('#credit_card_form'); if (!form) return; // Add your CreditCard-specific logic here // ... } function DirectDebit() { const sharedSettings = getSharedPaymentSettings(); if (!sharedSettings) return; const { stripe, elements, baseStyle } = initSharedStripe(sharedSettings); const form = document.querySelector('#direct_debit_form'); if (!form) return; // DirectDebit-specific value (only needed here) const country = document.querySelector('#server_side_constants').getAttribute('data-country'); // Add your DirectDebit-specific logic here // ... }
2. Use Factory Functions for Even More Encapsulation
If you want to wrap shared and specific logic together, factory functions (functions that return other functions/objects) are a great fit. They let you reuse shared setup while keeping each payment method’s unique logic separate:
// Factory function to create payment processors function createPaymentProcessor(formSelector, getExtraSettings = () => {}) { return function() { const sharedSettings = getSharedPaymentSettings(); if (!sharedSettings) return; const { stripe, elements, baseStyle } = initSharedStripe(sharedSettings); const form = document.querySelector(formSelector); if (!form) return; // Get any method-specific settings const extraSettings = getExtraSettings(sharedSettings); // Run method-specific initialization if (form.id === 'credit_card_form') { initCreditCard({ stripe, elements, baseStyle, form, sharedSettings }); } else if (form.id === 'direct_debit_form') { initDirectDebit({ stripe, elements, baseStyle, form, sharedSettings, ...extraSettings }); } }; } // Specific initialization functions function initCreditCard({ stripe, elements, baseStyle, form }) { // CreditCard-only logic here // ... } function initDirectDebit({ stripe, elements, baseStyle, form, country }) { // DirectDebit-only logic here (uses country) // ... } // Create your payment processor functions const CreditCard = createPaymentProcessor('#credit_card_form'); const DirectDebit = createPaymentProcessor('#direct_debit_form', () => { return { country: document.querySelector('#server_side_constants').getAttribute('data-country') }; });
How to Share Variables Between Functions
You’ve already seen one way (via shared config objects), but here are the most common, clean approaches:
- Shared Config Functions: As we did above, use functions like
getSharedPaymentSettings()to return a single object of shared values. All functions can call this to access the same data. - Module-Level Closures: If you’re using ES modules (or an IIFE for older code), you can define variables inside the module that only your payment functions can access. This avoids polluting the global scope:
Now both// Inside a module or IIFE let cachedStripeInstance = null; function initSharedStripe(settings) { if (!cachedStripeInstance) { cachedStripeInstance = Stripe(settings.stripeKey); } // ... rest of initialization }CreditCardandDirectDebitwill reuse the same Stripe instance instead of creating a new one. - Pass Shared Objects as Arguments: If you need to pass dynamic shared data, pass an object to your functions instead of defining it globally. This keeps dependencies explicit.
When Should You Use Classes?
Classes are fine if you need to create multiple instances of a payment processor with state (like tracking form submission status), but for your current scenario, composition with utility/factory functions is simpler and avoids over-engineering.
内容的提问来源于stack exchange,提问作者Tintin81

