Node.js模块导出:new Sample与Sample的选择及优劣分析
Great question—this is a super common decision point when working with prototype-based JavaScript and modules, so let’s break it down with clear scenarios and tradeoffs.
Core Difference First
First, let’s clarify what each export actually does:
module.exports = Sample: You’re exporting the constructor function. Consumers of your module will need to create their own instances withnew Sample()(or use it for inheritance).module.exports = new Sample(): You’re exporting a single, pre-instantiated object. Every part of your app that imports this module will use the exact same instance (this is effectively a singleton pattern).
When to Export the Constructor (Sample)
Look for these signals in your project design:
- You need multiple independent instances: For example, if you’re building a service class where each instance needs its own state (like a
UserServicewith unique API endpoints per instance, or a form handler tied to a specific DOM element). - You plan to extend the class: If other parts of your codebase will create subclasses with
class Child extends Sample, you must export the constructor—you can’t inherit from an instance. - Consumers need control over initialization: If your
Sampleconstructor requires parameters (e.g.,new Sample({ apiKey: 'xyz' })), letting consumers create instances lets them pass in their own configs. - Testability is a priority: Being able to create fresh instances for each test case avoids cross-test state pollution, making your tests more reliable.
Example of constructor export:
// payment-processor.js function PaymentProcessor(apiKey) { this.apiKey = apiKey; } PaymentProcessor.prototype.charge = function(amount) { return fetch('/api/charge', { method: 'POST', headers: { Authorization: `Bearer ${this.apiKey}` }, body: JSON.stringify({ amount }) }); }; module.exports = PaymentProcessor; // Usage const PaymentProcessor = require('./payment-processor'); const stripeProcessor = new PaymentProcessor('stripe-key-123'); const paypalProcessor = new PaymentProcessor('paypal-key-456'); // Two separate instances, each using their own API key
When to Export the Instance (new Sample())
Choose this approach if you see these design signals:
- You need a global singleton: For tools like a shared logger, config manager, or a state store where all parts of the app should read/write to the same state.
- The class is stateless or has fixed state: If all prototype methods are pure functions (no instance-specific properties), a single instance works fine (though a plain object might be even more intuitive here, but prototypes are okay too).
- Initialization requires no external input: If your
Sampledoesn’t need constructor arguments, and every consumer will use the exact same setup, pre-instantiating saves consumers from having to callnewevery time.
Example of instance export:
// app-logger.js function AppLogger() { this.logs = []; } AppLogger.prototype.log = function(level, message) { const logEntry = `[${level}] ${new Date().toISOString()}: ${message}`; this.logs.push(logEntry); console.log(logEntry); }; module.exports = new AppLogger(); // Usage in module A const logger = require('./app-logger'); logger.log('INFO', 'User authenticated'); // Usage in module B const logger = require('./app-logger'); logger.log('ERROR', 'Database connection failed'); // Both modules use the same instance—logs array contains both entries
Performance & Maintainability Tradeoffs
Performance
- Constructor export: Each
new Sample()creates a new object, which has trivial memory overhead in most cases. Unless you’re creating tens of thousands of instances in a hot loop, this won’t be a problem. Modern JS engines optimize object creation extremely well. - Instance export: Only one instance is created, so memory usage is fixed. The downside is shared state—if multiple modules modify the instance’s properties, you can get unexpected side effects (more on that below).
Maintainability
- Constructor export pros:
- Flexibility to support multiple use cases (different instances, inheritance).
- Easier to test: Fresh instances per test avoid state leaks.
- Clearer intent: Consumers know they’re dealing with a class that needs instantiation.
- Constructor export cons:
- Consumers must remember to use
new(though you can guard against this with a check in the constructor:if (!(this instanceof Sample)) return new Sample()).
- Consumers must remember to use
- Instance export pros:
- Simpler usage: Consumers can call methods directly without instantiating.
- Guarantees a single source of truth for shared state.
- Instance export cons:
- Shared state can lead to hard-to-debug bugs (e.g., Module A modifies the instance’s state, breaking Module B’s logic).
- Testing is harder: You’ll need to reset the instance’s state between tests or mock the entire module.
- No way to create independent instances if requirements change later (you’d have to refactor the export, breaking existing consumers).
Quick Decision Checklist
Ask yourself these questions:
- Do I need more than one instance of
Sample? → Export the constructor. - Will other classes inherit from
Sample? → Export the constructor. - Does
Sampleneed custom initialization parameters? → Export the constructor. - Is this a shared tool/service that should have a single state across the app? → Export the instance.
内容的提问来源于stack exchange,提问作者Joseph Ramos
相关产品推荐
相关产品推荐

