Modular programming is a very common Javascript programming model. It generally makes code easier to understand, but there are many good practices that are not widely known.
Basics
Let’s start with a brief overview of some modular patterns since Eric Miraglia (the developer of YUI) first published a blog describing the modular pattern three years ago. If you are already very familiar with these modular modes, you can skip this section and start reading from the "Advanced Mode".
Anonymous closure
This is the basic structure that makes everything possible, and it is also the best feature of Javascript. We will simply create an anonymous function and execute it immediately. All code will run within this function and live in a closure that provides privatization, which is enough to make the variables in these closures available throughout the entire life cycle of our application.
Note the pair of outermost parentheses surrounding the anonymous function. Because of the language characteristics of Javascript, this pair of parentheses is necessary. In JavaScript, statements starting with the keyword function are always considered function declarations. Wrapping this code in parentheses tells the interpreter that it is a function expression.
Global variable import
Javascript has a feature called implicit global variables. No matter where a variable name is used, the interpreter will follow the scope chain backwards to find the var declaration statement of the variable. If no var declaration is found, the variable is considered a global variable. If this variable is used in an assignment statement and the variable does not exist, a global variable will be created. This means it's easy to use or create global variables in anonymous closures. Unfortunately, this results in code that is extremely difficult to maintain, because to the human eye, it is impossible to tell which variables are global at a glance.
Luckily, our anonymous function provides an easy workaround. By simply passing global variables as arguments to our anonymous functions, we get cleaner and faster code than implicit global variables. Here is an example:
Module export
Sometimes you not only want to use global variables, you also want to declare them for repeated use. We can do this easily by exporting them - via the return value of the anonymous function. Doing this will complete a basic prototype of the modular pattern, followed by a complete example:
Note that we have declared a global module called MODULE, which has two public properties: a method called MODULE.moduleMethod and a variable called MODULE.moduleProperty. In addition, it maintains a private built-in state using anonymous function closures. At the same time, we can easily import the global variables we need and use this modular pattern as we learned before.
Advanced Mode
The foundation described in the above section is sufficient for many situations, and now we can develop this modular pattern further to create more powerful and extensible structures. Let's start with the MODULE module and introduce these advanced modes one by one.
Magnification mode
It is a limitation of modular mode that the entire module must be in one file. Anyone who has worked on a large project will understand the value of splitting js into multiple files. Fortunately, we have a great implementation for amplifying modules. First, we import a module, add properties to it, and finally export it. Here's an example - zoom it in from the original MODULE:
We use the var keyword to ensure consistency, although it is not required here. After this code is executed, our module already has a new public method called MODULE.anotherMethod. The amplification file also maintains its own private built-in state and imported objects.
Wide zoom mode
Our above example requires our initialization module to be executed first, and then the amplification module can be executed. Of course, sometimes this may not necessarily be necessary. One of the best things a Javascript application can do to improve performance is execute scripts asynchronously. We can create flexible multi-part modules and enable them to be loaded in any order through permissive enlargement mode. Each file needs to be organized according to the following structure:
In this pattern, the var expression is required. Note that if MODULE has not been initialized, this import statement will create MODULE. This means you can use a tool like LABjs to load all your module files in parallel without blocking.
Tight zoom mode
Wide zoom mode is great, but it also puts some limitations on your modules. Most importantly, you cannot safely override a module's properties. You also cannot use properties from other files during initialization (but you can use them at runtime). Tight amplification mode involves a sequential sequence of loads and allows overriding properties. Here's a simple example (zooming in on our original module):
We overrode the implementation of MODULE.moduleMethod in the above example, but can maintain a reference to the original method when needed.
Cloning and Inheritance
This mode is probably the least flexible option. It does make the code look cleaner, but that comes at the cost of flexibility. As I wrote above, if a property is an object or function, it will not be copied, but will become a second reference to the object or function. Modifying one of them will modify the other at the same time (Translator's Note: Because they are basically the same!). This object cloning problem can be solved through the recursive cloning process, but function cloning may not be able to solve it. Maybe eval can be used to solve it. Therefore, I describe this method in this article only for the sake of completeness.
Cross-file private variables
There is a major limitation in splitting a module into multiple files: each file maintains its own private variables, and cannot access the private variables of other files. But this problem can be solved. Here is an example of a permissive module that maintains private variables across files:
All files can set attributes on their respective _private variables, and it is understood that they can be accessed by other files. Once this module is loaded, the application can call MODULE._seal() to prevent external calls to the internal _private. If the module needs to be rescaled, internal methods in either file can call _unseal() before loading the new file, and call _seal() again after the new file has been executed. I use this pattern at work today, and I haven't seen this approach elsewhere. I think this is a very useful pattern, and it's worth writing an article on the pattern itself.
Submodule
Our last advanced mode is by far the easiest. There are many excellent examples of creating submodules. This is just like creating a regular module:
Although this may seem simple, I think it’s worth mentioning here. Submodules have all the advanced advantages of general modules, including amplification mode and privatization state.
Conclusion
Most advanced modes can be combined together to create a more useful mode. If I were to recommend a modular pattern for designing complex applications, it would be a combination of lenient enlargement patterns, private variables, and submodules.
I haven't thought about the performance issues of these patterns, but I'd rather translate this into a simpler way of thinking: if a modular pattern has good performance, then it can do a good job of minimizing, Makes downloading this script file faster. Using lenient amplification mode allows simple non-blocking parallel downloads, which results in faster downloads. Initialization time may be slightly slower than other methods, but the trade-off is worth it. As long as global variables are imported correctly, there should be no impact on runtime performance, and it's possible to get faster execution speeds by shortening reference chains with private variables in submodules.
To end, here is an example of a submodule dynamically loading itself into its parent module (creating the parent module if it does not exist). For the sake of simplicity, I removed the private variables. Of course, adding private variables is also very simple. This programming model allows an entire complex hierarchical code base to be loaded in parallel via submodules.
This article summarizes the current best practices of "Javascript modular programming" and explains how to put them into practice. Although this is not an introductory tutorial, you can understand it as long as you have a little understanding of the basic syntax of Javascript.