This article was translated from Ben Cherry's "JavaScript Module pattern:in-depth". Although individuals do not quite agree with the necessity of the existence of private variables in JS, this article is a very comprehensive introduction to the modular mode of JavaScript in all aspects. After I read it, I benefited from it, so I hope I can help you with my translation.
This article was originally posted in my personal blog: http://jerryzou.com/posts/jsmodular/
Modular programming is one of the most common JavaScript programming patterns. It can generally make the code easier to understand, but there are many good practices that are not well known. In this article, I will review the basics of JS modular programming, and will talk about some really noteworthy advanced topics, including a pattern I think I have created myself.
Basis
Let's start with a brief overview of some of the modular patterns that have been made since Eric Miraglia (the developer of Yui) for the first time since three years ago to describe the modular model. If you are already familiar with these modular modes, you can skip this section and start reading from "Advanced Mode".
Anonymous closures
It's a basic structure that makes everything possible, and it's also the best feature of JavaScript. We will simply create an anonymous function and execute it immediately. All the code will run inside this function, living in a closed package that provides privatization, enough to make the variables in these closures run through the entire lifecycle of our application.
(function () { //... all vars and functions is in this scope is only //still maintains access to all Globals} ());
Note the outermost parentheses of the anonymous function for the package. This pair of parentheses is necessary because of the language characteristics of JavaScript. A statement that starts with a keyword function in JS is always considered a functional declaration. Wrapping this piece of code in parentheses allows the interpreter to know that it is a function expression.
Global variable Import
JavaScript has an attribute called an implicit global variable. Regardless of where a variable name is used, the interpreter will find the declaration statement of the variable in the reverse direction based on the scope chain var
. If the declaration statement is not found var
, then the variable is considered a global variable. If the variable is used in an assignment statement, and the variable does not exist, a global variable is created. This means that it is easy to use or create global variables in an anonymous closure. Unfortunately, this can cause the code to be written to be extremely difficult to maintain, because for a person's intuitive perception, it is not clear that the variables are global.
Fortunately, our anonymous function provides a simple workaround. As long as the global variable is passed as a parameter to our anonymous function, we can get a clearer and faster code than the implicit global variable. Here's an example:
(function ($, yahoo) { //now has access to Globals JQuery (as $) and Yahoo. This code} (jquery, Yahoo));
Module Export
Sometimes you want to not only use global variables, you also want to declare them for reuse. We can easily export them to do this-through the return value of the anonymous function. Doing so will complete a basic prototype of modular mode, followed by a complete example:
var MODULE = (function () { var my = {}, privatevariable = 1; function Privatemethod () { //... } My.moduleproperty = 1; My.modulemethod = function () { //... }; return my;} ());
Note that we have declared a MODULE
global module called, which has 2 public properties: A MODULE.moduleMethod
method called and a MODULE.moduleProperty
variable called. In addition, it maintains a private, built-in state that takes advantage of anonymous function closures. At the same time, we can easily import the required global variables and use this modular pattern as we have learned before.
Advanced Mode
The basis described in the previous section is sufficient to deal with many situations, and now we can further develop this modular model to create more powerful, extensible structures. Let's MODULE
start with the module and introduce these advanced modes.
Magnification mode
The entire module must be a limitation of the modular mode in one file. Anyone involved in a large project will understand the value of splitting JS into multiple files. Fortunately, we have a great implementation to zoom in on the module. First, we import a module, add properties to it, and finally export it. Here's an example-zoom in on it from the original MODULE
:
var MODULE = (function (my) { My.anothermethod = function () { //Added method ... }; return my;} (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, MODULE.anotherMethod
public method called. This zoomed-in file also maintains its own private built-in state and imported objects.
Wide magnification mode
Our above example requires that our initialization module be executed first, then the module can be scaled to execute, and sometimes this may not necessarily be necessary. One of the best things a JavaScript app can do to improve performance is to execute scripts asynchronously. We can create flexible multipart modules and enable them to be loaded in any order by using the wide magnification mode. Each file needs to be organized according to the following structure:
var MODULE = (function (my) { //Add capabilities ... return my;} (MODULE | | {}));
In this pattern, the var
expression makes the required. Note If the module has not been initialized, the import statement is created MODULE
. This means that you can use a tool like LABJS to load all of your module files in parallel without being blocked.
Compact magnification mode
The wide magnification mode is great, but it also brings some limitations to your module. Most importantly, you cannot safely override the properties of a module. You also can't use attributes from other files while initializing (but you can use them at run time). The compact magnification mode contains a sequential sequence of loads and allows overriding properties. Here is a simple example (enlarge our original MODULE
):
var MODULE = (function (my) { var old_modulemethod = My.modulemethod; My.modulemethod = function () { //method override, have access to old through Old_modulemethod ... }; return my;} (MODULE));
We covered the implementation in the example above MODULE.moduleMethod
, but when needed, we can maintain a reference to the original method.
Cloning and inheritance
var Module_two = (function (old) { var my = {}, key; For (Key-in-old) { if (Old.hasownproperty (key)) { My[key] = Old[key];} } var super_modulemethod = Old.modulemethod; My.modulemethod = function () { //override method on the clone, access to super through Super_modulemethod };
return my;} (MODULE));
This pattern is probably one of the most inflexible options. It does make the code look neat, but it's the price 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 these changes the other at the same time (translator Note: Because they are all one!) )。 This can be done through the recursive cloning process to solve this object cloning problem, but the function cloning may not be solved, perhaps with eval can solve it. Therefore, I have described this approach in this article only to take into account the integrity of the article.
Cross-file private variables
There is a significant 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 wide magnification module that maintains a cross-file private variable:
var MODULE = (function (my) { var _private = my._private = My._private | | {}, _seal = my._seal = My._seal | | function () { delete my._private; Delete my._seal; Delete my._unseal; }, _unseal = my._unseal = My._unseal | | function () { my._private = _private; My._seal = _seal; My._unseal = _unseal; }; Permanent access to _private, _seal, and _unseal return my;} (MODULE | | {}));
All files can have _private
properties set on their respective variables, and it understands that they can be accessed by other files. Once this module is loaded, the application can be called MODULE._seal()
to prevent external calls to the internal _private
. If the module needs to be magnified, the internal method in any file can be called before the new file is loaded _unseal()
and called again after the new file is executed _seal()
. I use this pattern at work now, and I haven't seen it anywhere else. I think it's a very useful model and it's worth writing an article about the pattern itself.
Sub-module
Our final advanced model is obviously the simplest. There are a number of good examples of creating sub-modules. This is like creating a generic module:
Module.sub = (function () { var my = {}; // ... return my;} ());
Although this may seem simple, I think it is worth mentioning here. Sub-modules have the advanced advantages of all general modules, including amplification mode and privatization status.
Conclusion
Most advanced modes can be combined to create a more useful pattern. If I were to recommend a modular pattern for designing complex applications, I would choose to combine wide magnification, private variables, and sub-modules.
I haven't considered the performance issues of these patterns yet, but I'd rather turn this into a simpler way of thinking: if a modular model has good performance, it can do a great job of minimizing it, making it faster to download the script file. Using the wide magnification mode allows for simple non-blocking parallel downloads, which speeds up the download speed. The initialization time may be slightly slower than other methods, but it is worthwhile to weigh the pros and cons. As long as global variables are imported accurately, run-time performance should not be affected, and it may be possible to run faster by shortening the reference chain with private variables in the submodule.
As an end, here is an example of a submodule dynamically loading itself into its parent module (creating it if the parent module does not exist). For brevity, I have removed the private variables, and of course the private variables are very simple. This programming pattern allows a whole complex hierarchy of code libraries to be loaded in parallel through a submodule.
var UTIL = (function (parent, $) { var my = Parent.ajax = Parent.ajax | | {}; My.get = function (URL, params, callback) { //OK, so I ' m cheating a bit:) return $.getjson (URL, params, callback ); }; etc... return parent; (UTIL | | {}, jQuery));
I hope this article will help you, please leave a message under the article to share your thoughts. From now on, start writing better, more modular JavaScript!
Gain insight into JavaScript modular programming