SoFunction
Updated on 2025-03-10

In-depth analysis of Module mode programming in JavaScript

Basic knowledge

First of all, we need to have a rough understanding of the Module mode (proposed in the blog by YUI's Eric Miraglia in 2007). If you are already familiar with the Module mode, you can skip this section and read "Advanced Mode".

Anonymous function closure

Anonymous function closures are the best feature of JavaScript, and no doubt that it makes everything possible. Now let's create an anonymous function and execute it immediately. All code in a function is executed in a closure, which determines the privacy and state of these codes throughout the execution process.

Copy the codeThe code is as follows:

(function () {
 // ... all vars and functions are in this scope only
 // still maintains access to all globals
}());

Note the brackets outside the anonymous function. This is because statements starting with a function in JavaScript are usually considered function declarations. After adding the outer brackets, the function expression is created.

Global import

JavaScript has a feature called hidden global variables. When a variable name is used, the compiler will query the superior to declare the statement that var. If not found, this variable is considered global. If used in this way when assigning values, a global scope will be created. This means it is very easy to create a global variable in an anonymous closure. Unfortunately, this will make the code difficult to manage, because it will be unclear for programmers to declare global variables in a file. Fortunately, the anonymous function gives me another option. We can import global variables into our code through the parameters of anonymous functions, which is faster and cleaner.

Copy the codeThe code is as follows:

(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));

Module export

Sometimes you don't want to use global variables, but you want to declare them. We can easily derive them by the return value of anonymous functions. There is only so much basic content about the Module mode, here is a more complicated example.

Copy the codeThe code is as follows:

var MODULE = (function () {
 var my = {},
  privateVariable = 1;

 function privateMethod() {
  // ...
 }

  = 1;
  = function () {
  // ...
 };

 return my;
}());

Here we declare a global module called MODULE, which has two public properties: a method called and a variable called. In addition, it maintains the private internal state through the closure of anonymous functions. Of course, we can also use the aforementioned pattern to easily import the required global variables

Advanced Mode

The content mentioned earlier can already meet many needs, but we can study this model more deeply to create some powerful scalable structures. Let us continue to learn little by little through this module called MODULE.

expand

At present, one limitation of the module pattern is that the entire module must be written in a file. Everyone who has done large-scale code development knows the importance of separating a file into multiple files. Fortunately we have a great way to expand modules. First we import a module, then add the attribute, and finally export it. The example here is to expand MODULE using the method mentioned above.

Copy the codeThe code is as follows:

var MODULE = (function (my) {
  = function () {
  // added method...
 };

 return my;
}(MODULE));

Although unnecessary, for consistency, we use the var keyword again. Then the code is executed, and the module will add a public method called. This extension file also maintains its private internal state and import.

Sleep expansion

The example above requires us to create a module first and then expand the module, which is not necessary. Asynchronous loading scripts is one of the best ways to improve Javascript application performance. . Through loose expansion, we create flexible modules that can be loaded in any order and divided into multiple files. The structure of each file is roughly as follows

Copy the codeThe code is as follows:

var MODULE = (function (my) {
 // add capabilities...

 return my;
}(MODULE || {}));

In this mode, the var statement is required. If the imported module does not exist, it will be created. This means you can use a tool similar to LABjs to load files of these modules in parallel.

Tightly expand

Although Sponsored Expansion is already great, it also adds some limitations to your module. The most important thing is that you have no way to safely override the module properties, and you cannot use the module properties in other files during initialization (but you can use them in the run after initialization). Tight expansion includes a certain loading order, but supports rewriting. Here is an example (expanded our original MODULE).

Copy the codeThe code is as follows:

var MODULE = (function (my) {
 var old_moduleMethod = ;

  = function () {
  // method override, has access to old through old_moduleMethod...
 };

 return my;
}(MODULE));

We have rewritten it here and retained references to the original method as required.

Copy and inheritance

Copy the codeThe code is as follows:

var MODULE_TWO = (function (old) {
 var my = {},
  key;

 for (key in old) {
  if ((key)) {
   my[key] = old[key];
  }
 }

 var super_moduleMethod = ;
  = function () {
  // override method on the clone, access to super through super_moduleMethod
 };

 return my;
}(MODULE));

This model is probably the least flexible option. While it supports some elegant mergers, it comes at the expense of dexterity. In the code we write, properties whose types are objects or functions will not be copied, but will only exist in the form of two references to one object. One changes, the other changes too. For objects [g5], we can solve them through a recursive cloning operation, but there is no way to do it for functions except eval. However, for the sake of completeness I still include it.

Private state across files

There is a big limitation to dividing a module into multiple files, that is, each file maintains its own private state, and there is no way to obtain the private state of other files. This can be solved. The following loosely expanded example can maintain private state in different files.

Copy the codeThe code is as follows:

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 || {}));

Each file can set properties for its private variable_private, and other files can be called immediately. When the module is loaded, the program will call MODULE._seal(), making it impossible for the outside to touch the internal _.private. If the module needs to be expanded again later, a certain attribute needs to be changed. Before loading a new file, each file can call _.unsea(), and then call _.seal after the code is executed.

This model I have thought of in my work today and I have never seen it anywhere else. But I think this is a useful pattern and worth writing it out separately.

Sub-modules

The last advanced mode is actually the easiest, with many examples of creating submodules, just like creating a general module.

Copy the codeThe code is as follows:

= (function () {
 var my = {};
 // ...

 return my;
}());

While this may be simple, I decided it was worth writing in. Sub-modules have all the high-quality features of general modules, including expansion and private state.

Summarize

Most advanced modes can be combined with each other to create new and more useful modes. If I had to propose a method to design complex applications, I would combine loose expansion, private state, and submodule.

I don't mention performance-related things here, but I can say that module mode is good for performance improvements. It can reduce the amount of code, which makes the code load faster. Slack expansion makes parallel loading possible, which also improves loading speed. The initialization time may be longer than other methods, but it is worth it. As long as the global variable is correctly imported and run, there will be no problem. In the submodule, the reference chain to the variable may also increase the speed.

Finally, here is an example of a submodule loading itself dynamically (created if it does not exist), for the sake of an introduction I did not consider the internal state, but it was simple even if it was considered. This pattern allows complex, multi-level code to be loaded in parallel, including submodules and everything else.

Copy the codeThe code is as follows:

var UTIL = (function (parent, $) {
 var my = = || {};

  = function (url, params, callback) {
  // ok, so I'm cheating a bit
  return $.getJSON(url, params, callback);
 };

 // etc...

 return parent;
}(UTIL || {}, jQuery));

I hope these contents are useful, please leave a message below to share your thoughts. Teenagers, work hard and write better, more modular JavaScript.

Original link:ben cherryTranslation: Wang Xiao