Build Your Own IoC Container in JavaScript: A Step‑by‑Step Guide
This tutorial walks you through creating a simple Inversion of Control (IoC) container in JavaScript, covering basic dependency injection, adding caching for singleton instances, and extending the container to load arbitrary classes automatically, all with clear code examples.
In the Java ecosystem, Spring Framework has become the de‑facto standard, and large projects often rely on an IoC container. If you are unfamiliar with Dependency Injection (DI) or IoC, this guide will teach you from scratch.
We start with a minimal requirement: given several classes, we want a simple method to obtain an instance of each class.
class A {
b: B;
}
class B {
c: C;
}
class C {
hello() {
console.log('hello world');
}
}
const container = new Container();
const a = container.get(A);
// a.b.c.hello() === 'hello world'First Step
The Container class must analyze a class’s properties to discover its dependencies. Assuming all properties are standard classes, we can recursively create the required objects.
class Container {
get(Module) {
// create object
const obj = new Module();
// get property names
const properties = Object.getOwnPropertyNames(obj);
for (let p of properties) {
if (!obj[p]) {
if (p === 'b') {
obj[p] = this.get(B);
} else if (p === 'c') {
obj[p] = this.get(C);
} else {}
}
}
}
}This recursive get method creates missing dependencies on demand.
Second Step
To avoid creating multiple instances of the same class, we introduce a cache so each class is instantiated only once per run.
class Container {
cache = {};
getName(Module) {
return Module.name.toLowerCase();
}
get(Module) {
// cache lookup
if (this.cache[this.getName(Module)]) {
return this.cache[this.getName(Module)];
}
// create object
const obj = new Module();
// store in cache
this.cache[this.getName(Module)] = obj;
const properties = Object.getOwnPropertyNames(obj);
for (let p of properties) {
if (!obj[p]) {
if (p === 'b') {
obj[p] = this.get(B);
} else if (p === 'c') {
obj[p] = this.get(C);
} else if (p === 'd') {
obj[p] = this.get(D);
} else {}
}
}
}
}With caching, each class has a single instance during execution.
Third Step
To support an arbitrary number of classes, we add a scanning mechanism that populates a classTable map with all exported classes found in the project.
init() {
const fileResults = globby.sync(['**/**.ts', '**/**.js'], {
cwd: process.cwd(),
ignore: ['**/node_modules/**'],
});
for (const name of fileResults) {
const exports = require(process.cwd() + '/' + name);
// store class name and class reference
this.classTable[this.getName(exports)] = exports;
}
}The container can now retrieve any class by name without hard‑coding dependencies.
class Container {
cwd = process.cwd();
cache = {};
classTable = {};
init() {
const fileResults = globby.sync(['**/**.ts', '**/**.js'], {
cwd: this.cwd,
ignore: ['**/node_modules/**'],
});
for (const name of fileResults) {
const exports = require(this.cwd + '/' + name);
this.classTable[this.getName(exports)] = exports;
}
}
getName(Module) {
return Module.name.toLowerCase();
}
get(Module) {
if (this.cache[this.getName(Module)]) {
return this.cache[this.getName(Module)];
}
const obj = new Module();
this.cache[this.getName(Module)] = obj;
const properties = Object.getOwnPropertyNames(obj);
for (let p of properties) {
if (!obj[p]) {
if (this.classTable[p]) {
obj[p] = this.get(this.classTable[p]);
}
}
}
return obj;
}
}Final Step
Congratulations! You now have a working IoC container you can discuss in interviews. In real projects you would typically use a mature library such as injection or the midway framework, but building your own helps you understand dependency injection and decoupling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
