Here, we'll discuss dependency injection (DI) in the context of "composition over inheritance."
When adopting composition, there are often many function compositions, and even compositions of compositions. This essentially creates a dependency chain among functions.
If you hard-code the function combinations directly into your code, creating a new higher-order composition that differs from an existing one only in the implementation of a single base function would require duplicating the original composition code and modifying one function call.
If you want to reuse the existing higher-order composition but only replace the base function, you'd either need to pass the function as a parameter (which might involve a long parameter chain) or use dependency injection. DI exists precisely to address this issue.
With DI, you only need to call functions through injected dependencies when writing the code. Later replacements or adjustments become much easier.
How to Implement Dependency Injection
Dependency injection is fundamentally linked to a chain structure. JavaScript's prototype chain can be considered a form of dependency injection.
To implement DI, you just need two functions: inject and provide.
provide: Accepts a key and value, binding them to the current node.
inject: Accepts a key. Starting from any node, it traverses up the chain, looking for the key in the parent nodes. If found, it returns the associated value; otherwise, it continues up the chain until the root node.
The implementation of inject is essentially the same as JavaScript's prototype chain mechanism for looking up property values. That's why I say JavaScript's prototype chain can be considered a form of dependency injection.
These two concepts are straightforward. In fact, the most challenging part to understand is the idea of "chain-based structures."
Example: Vue's Dependency Injection System
In Vue, the chain structure is based on the component tree. Each component can be viewed as a node, and dependency injection involves traversing up the tree from one component to its parent, continuing until the root. All the nodes along this traversal form a chain. With this chain, the deepest node can access any provide value from its parent components. If multiple nodes provide values with the same key, the inject function retrieves the nearest parent node's provide value.
Can Dependency Injection Be Implemented Using Functions as Nodes?
What if we wanted to treat functions as nodes and use the call stack as the chain to implement a DI system?
The answer is both yes and no. In the browser, this approach is limited and can only be implemented using zone.js (which has its limitations).
However, in Node.js, you can use Async hooks to achieve a similar goal.