Core JavaScript · 1 of 9
A module's top-level code runs once. The first import of a file evaluates it; every later import of that same file resolves to the module that was already evaluated. Top-level bindings are therefore shared by every importer, not recreated per import.
With a class, state belongs to an instance, so sharing it means making sure every caller gets the same instance:
export class ApiClient {
private cache = new Map<string, Response>();
async get(url: string): Promise<Response> {
if (!this.cache.has(url)) {
this.cache.set(url, await fetch(url));
}
return this.cache.get(url)!;
}
}
Nothing enforces that. Two files can each construct one and quietly get separate caches:
// file-a.ts
const client = new ApiClient();
// file-b.ts
const client = new ApiClient(); // its own cache — no error, no warning
Move the state to the top level of a module and the sharing is structural — cache is created once, when the module first evaluates:
// api-client.js
const cache = new Map();
export const get = async (url) => {
if (!cache.has(url)) {
cache.set(url, await fetch(url));
}
return cache.get(url);
};
// file-a.js
import { get } from "./api-client.js";
// file-b.js
import { get } from "./api-client.js"; // same module, same cache
Modules are keyed by resolved path, so both imports return the same evaluated module no matter how many files ask for it or what order they load in. One shared instance is the default, guaranteed by the module system rather than by wiring the same object through your app.