📜 ⬆️ ⬇️

JavaScript modular approach

A modular approach is a fairly common JavaScript programming technique. Usually it is understood quite well, but advanced techniques are not well described. In this article I will review the basics and touch on some complex techniques, including one, in my opinion, original.

The basics



We'll start with a simple review of the modular approach, well known since YUI first wrote about this from Eric Miraglia of YUI. If you already know the modular approach, go directly to the “Advanced Techniques”.
')

Anonymous closures



This basic construction is the basis of everything, and the best is actually in javascript. We simply create an anonymous function, and immediately execute it. All executable code lives inside a closure that ensures privacy and state preservation throughout the life of the entire application.

( function ( ) {
// ... all vars and functions only within this context
// still have access to global variables
} ( ) ) ;


Note the () around the anonymous function. This is required by language, since statements that begin with the word function are always interpreted as function declarations. Adding () creates a functional expression instead.

Global import



JavaScript supports the so-called default global. Upon encountering the variable name, the interpreter traverses the context chain backwards in search of the var operator for that name. If it is not found, the variable is assumed global. If it is used in an assignment, a global one is created if it has not already been there. This means that using or creating global variables in anonymous closures is very simple. Unfortunately, this leads to poorly supported code, so (people) it is not obvious which variables are global in this file.

Fortunately, our anonymous function offers a simple alternative. Passing global as parameters to an anonymous function, we import them into our code, which is clearer and faster than the default global ones. For example:

( function ( $ , YAHOO ) {
// now the code has access to jQuery variables (like $) and YAHOO
} ( jQuery , YAHOO ) ) ;


Export module



Sometimes you want to not just use global, you want to declare them. We can do this easily by exporting them via the return value of an anonymous function. This technique completes the basic modular approach; here is a complete example:

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

function privateMethod ( ) {
// ...
}

my. moduleProperty = 1 ;
my. moduleMethod = function ( ) {
// ...
} ;

return my ;
} ( ) ) ;

Notice that we declared a global module called MODULE with two public members: a method called MODULE.moduleMethod and a variable named MODULE.moduleProperty. In addition, it stores a separate internal state using the closure of an anonymous function, plus we can easily import global variables using the previous approach.

Advanced approaches



Despite the fact that in many cases, the above techniques are enough, we can improve them and create very powerful, expandable designs. Consider them in turn, starting with our module named MODULE.

Replenishment



One of the limitations of the modular approach is that the entire module must be contained in a single file. Anyone who has worked with large programs understands the meaning of breaking the code into several files. Fortunately, there is an elegant solution for adding modules. First we import the module, then add the members, and then export it. Here is an example with the completion of our MODULE module:

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

return my ;
} ( MODULE ) ) ;

We here again use var for uniformity, although this is not necessary. After this code is executed, our module will have a new public method called MODULE.anotherMethod. The replenishment file will also store its own state and imported variables.

Free top up



The previous examples relied on the module created in advance and replenished later, but it can be done differently. The best thing a JavaScript application can do to improve performance is to load scripts asynchronously. We can make flexible modules, broken into pieces, that load themselves in any order using free replenishment. Each file should have the following structure:

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

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

In this scheme, the var operator is always needed. Note that the import will create a module if it has not already been. This means that you can use utilities like LABjs and load all your files with modules in parallel, without blocking.

Limited replenishment



Free replenishment is good, but it imposes limitations, the most important of which is that you cannot safely override the members of the module. In addition, you cannot use module members from other files during initialization (but you can after it) completes. Limited replenishment sets the boot order, but allows overriding. Here is a simple example (updating our old MODULE):

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

my. moduleMethod = function ( ) {
// method override, has old_moduleMethod ...
} ;

return my ;
} ( MODULE ) ) ;

Here we redefined the MODULE.moduleMethod, but retained a reference to the original method, if needed.

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 for super_moduleMethod
} ;

return my ;
} ( MODULE ) ) ;

This approach brings some elegance, but at the expense of flexibility. Members that are objects or functions are not duplicated; they continue to exist as one object with two names. Changing one changes and the second. For objects, this can be fixed through recursive cloning, but the functions, it seems, do nothing to help, except through eval. Anyway, I included it to complete the picture.

Cross-file state



A serious limitation of partitioning a module into files is that each stores its own state, and does not see the state of other files. Here is an example of a free-supplemented module that stores state despite all updates:

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

Any file can set members on the local _private variable, and they will be immediately accessible from the outside. Once this module is fully loaded, the application should call MODULE.seal (), which prevents external access to the internal _private. If we want to replenish the module even during the lifetime of the application, one of the internal methods can call _unseal () before loading a new file, and then _seal () after its execution.

It occurred to me at work today, I have not seen this before. I think that this is a very useful approach, and it is worth a separate description.

Submodules



Our latest advanced approach is the easiest. There are many reasons for creating submodules. This is how to create a regular module:

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

return my ;
} ( ) ) ;

No matter how obvious it was, it seemed to me worth mentioning. Sub-modules have all the properties of conventional modules, including replenishment and state preservation.

findings



Most advanced approaches can be combined. When creating a complex application, I would personally choose free replenishment, private states and sub-modules.

I didn’t touch upon the performance issues at all, but I can briefly say: the modular approach is productive. It is well minified, which speeds up loading. Free completion allows non-blocking parallel loading, which also speeds up code launch. The initialization time is most likely longer than in other approaches, but it is worth it. Runtime performance should not suffer if global variables are correctly imported, and it will probably be even better in submodules due to the reduction of the chain of variables with the help of local ones.

Finally, here is a submodule that is dynamically loaded by its parent (and, if necessary, created). I missed saving state for short, but it's easy to add. This approach allows you to load complex hierarchical code completely in parallel, with submodules and so on.

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

Hope you enjoyed it, share your thoughts. And now go ahead, writing in JavaScript is modular!

Source: https://habr.com/ru/post/117069/


All Articles