Analysis and example of JavaScript modular programming

Source: Internet
Author: User
Tags anonymous closure define function eval shallow copy

A module is an indispensable part of any large-scale application architecture. It allows us to clearly separate and organize code units in the project. In project development, by removing dependencies, loose coupling can improve the maintainability of applications. Unlike other traditional programming languages, JavaScript does not provide native and organized introduction modules. This article will discuss several common modular solutions.

1. Representation of object literal

Objects can be considered as objects that contain a set of key-value pairs. Each key-value pair is separated by a colon. Objects do not need to be instantiated using the new operator. You can add attributes and methods to objects outside the object. Example:

Var myModule = {
MyProperty: "jeri ",

// The object literal can contain attributes and methods
// For example, you can declare the configuration object of the module.
MyConfig :{
UseCaching: true,
Language: "en"
},

// Basic method
MyMethod1: function (){
Console. log ("method1 ");
},

// Output information based on the current configuration
MyMethod2: function (){
Console. log ("Caching is:" + '(this. myConfig. useCaching )? "Enabled": "disabled "');
},

// Output information based on the current configuration
MyMethod3: function (newConfig ){
        
If (typeof newConfig = "object "){
This. myConfig = newConfig;
Console. log (this. myConfig. language );
        }
    }
}
As mentioned above, using object literally helps encapsulate and organize code, and then different object literal modules form complex projects.


2. Module mode

The Module model was initially defined in traditional software engineering to provide private and public encapsulation methods for classes. In JavaScript, classes cannot be declared directly. However, we can use closures to encapsulate private attributes and methods, simulate the concept of classes, and implement the Module mode in JavaScript. In this way, we make a separate object have public/private methods and variables, thus shielding special parts from the global scope, this greatly reduces the possibility of conflicts between variable declarations and function declarations.


Var myModule = (function (){

// Private variable
Var privateVar = 0;

// Private function
Var privateFun = function (foo ){
Console. log (foo );
};

Return {
// Private variable
PublicVar: "foo ",

// Public functions
PublicFun: function (arg ){

// Modify private variables
PrivateVar ++;

// Pass in the bar to call the private method
PrivateFun (arg );
        }
};
})();


As shown above, we use closures to encapsulate private variables and methods, and only expose one interface for other calls. Private variables (privateVar) and methods (privateFun) are confined to the closure of the module and can only be accessed through public methods. In this mode, apart from returning an object rather than a function, it is very similar to calling a function expression immediately. We can add new attributes and methods for the returned object, these new attributes and methods are available to external callers.

The Module mode JavaScript implementation is very concise for people with object-oriented development experience, but it also has its own shortcomings and disadvantages.

Because we access public and private members in different ways, when we want to change the visibility, we need to modify the location where this member was used, which is not conducive to maintenance and upgrade, and the coupling is not ideal. In addition, in the newly added method, we cannot access the previously declared private methods and variables, because the closure is only bound at creation. We cannot create automated unit tests for private methods, and it is extremely difficult to modify private methods. We need to rewrite all public methods that interact with private methods. The workload of bug fixes will be huge. In addition, we cannot easily expand private methods.


About the script loader

To discuss AMD and CommonJS modules, we must talk about an obvious topic-the script loader. Currently, script loading is intended to enable modular JavaScript in today's applications. There are many loaders used to load modules in AMD and CommonJS methods. The most famous ones are RequireJS and curl. js. You can understand the usage and running mechanism of the script loader on your own.


3. AMD module

AMD stands for Asynchronous Module Definition, that is, Asynchronous Module loading mechanism. It was born from the Dojo development experience using XHR + eval. Its overall goal is to provide modular JavaScript solutions to avoid any future solutions being affected by the shortcomings of previous solutions. The AMD module format itself is a recommendation for defining modules. Both modules and dependencies can be asynchronously loaded, and it is highly flexible, removing the common tight coupling between code and modules.

AMD has two very important concepts: the define method defined by the module and the require method used to process dependency loading.
As a specification, you only need to define its syntax API, but do not care about its implementation. The define function is defined as follows:

Define (
[Module-name?] /* Optional */,
[Array-of-dependencies?] /* Optional */,
[Module-factory-or-object]
);

Where:

Module-name: module ID, which can be omitted. Without this attribute, it is called the anonymous module.
Array-of-dependencies: the dependent module, which can be omitted.
Module-factory-or-object: module implementation, or a JavaScript object.

Example:

Define (
"MyModule ",
["Foo", "bar"],

// Define the function of the module, and map the dependency (foo, bar) to the function as a parameter.
Function (foo, bar ){
// Create a module
Var myModule = {
MyFun: function (){
Console. log ("Jeri ");
            }
        }

// Return the defined module
Return myModule;
    }
);


Require is used to load JavaScript file or module code and obtain dependencies. Example:


// Foo, bar is an external module, and the loaded output is passed in as a callback function parameter for access
Requrie (["foo", "bar"], function (foo, bar ){

// Other code
Foo. doSomething ();
});


The following is an example of dynamic dependency loading:


Define (
Function (requrie ){
Var isReady = false,
Foobar;

Requrie (["foo", "bar"], function (foo, bar ){
IsReady = true,
Foobar = foo () + bar ();
});

// Return the defined module
Return {
IsReady: isReady,
Foobar: foobar
};
    }
);


The AMD module can use plug-ins, that is, when we Load dependencies, we can load files in any format. AMD provides clear suggestions on how to define flexible modules. Using AMD to write modular JS code is more concise than the existing global namespace and <script> label solution, there is no global namespace pollution. You can also delay loading scripts as needed.


4. CommonJS module

The CommonJS specification suggests specifying a simple API to declare a module that works outside the browser. Unlike AMD, it tries to include a wider range of interesting issues, such as IO and file systems.

In terms of structure, the CommonJS module is a reusable part of JS and exports a specific object so that it can be used for any dependent code. Unlike AMD, the CommonJS module does not use define for definition. The CommonJS module consists of two parts: the variable exports and the require function. Exports contains an object that the module wants other modules to use. The require function is used to import and export data from other modules, that is, to load dependencies of other modules. Example:
Lib. js


// Newly defined module method
Function log (arg ){
Console. log (arg );
}

// Expose the method to other modules
Exports. log = log;


App. js


//./Lib is a dependency we need
Var lib = requrie ("./lib ");

// Newly defined module method
Function foo (){
Lib. log ("jeri ");
}

// Expose the method to other modules
Exports. foo = foo;


Although the CommonJS organization module can be used on the browser side, many developers think that CommonJS is more suitable for server-side development, because many CommonJS APIs have server-oriented features such as io and system. NodeJs uses the CommonJS specification. When a module may be used on the server side, some developers tend to choose CommonJS, while AMD is used in other cases.

AMD modules can use plug-ins. That is to say, when we Load dependencies, we can load files in any format and define more fine-grained things, such as constructors and functions, however, the CommonJS module can only define objects that are not easy to use. The definition and introduction of modules are also very different. Both AMD and CommonJS are excellent module models with different goals.

AMD adopts browser-preferred development methods to select asynchronous behavior and simplified backward compatibility, but does not have any file I/O concept. Supports object, function, constructor, and other types of objects, which are generated and run in the browser.
CommonJS adopts the server-first approach. Assuming that synchronous behavior has no global conceptual burden, only objects are supported as modules. CommonJS supports non-packaging modules, closer to the next generation of ES Harmony specifications.

5. ES Harmony module

TC39-a standard group responsible for developing ECMAScript syntaxes, semantics, and future iterations. In recent years, we have been paying close attention to the evolution of JavaScript usage in large-scale development, in addition, it is also sensitive to the need for better language features to write more modular JS. For this reason, a series of exciting language supplements have been proposed. Although Harmony is still in the suggested stage, we can first look at the new interface features.


Imports and Exports modules

In ES. next, we have provided a more concise method for module dependency and module export, namely import and export.

The import declaration binds a module to be exported as a local variable and can be renamed to avoid name conflicts.
Export declares the local binding of an external visible module. Other modules can read the export but cannot modify it. You can export sub-modules, but cannot export modules defined elsewhere. Export can also be renamed.

Cakes. js


Module staff {
// Specify the export
Export var baker = {
Bake: function (item ){
Console. log ('Woo! I just baked '+ item );
        }
    }
};

Module skills {
Export var specialty = "baking ";
Export var experience = "5 years ";
};

Module cakeFactory {
// Specify the dependency
Import baker from staff;

// Import everything using wildcards
Import * from skills;

Export var oven = {
MakeCupcake: function (toppings ){
Baker. bake ('cupcak', toppings );
},
MakeMuffin: function (mSize ){
Baker. bake ('muffin', size );
        }
    }
};


Remote loading module

We recommend that you support remote module loading in ES. next, for example:


Module cakeFactory from 'http: // *****/cakes. Js ';

CakeFactory. oven. makeCupcake ('sprinkles ');

CakeFactory. oven. makeMuffin ('large ');


Module loader API

The module loader recommends that a dynamic API load the module in strictly controlled context. The loader supports load (url, moduleInstance, error), createModule (object, globalModuleReferences), and so on.


CommonJS module for servers

For server-oriented developers, the module system proposed in ES. next is not limited to browser-side modules. For example, the following is a server-side CommonJS module:
File. js


// Io/File. js
Export function open (path ){
//...
};
Export function close (hnd ){
//...
};


LexicalHandler. js


// Compiler/LexicalHandler. js
Module file from 'io/file ';

Import {open, close} from file;
Export function scan (in ){
Try {
Var h = open (in )...
} Finally {
Close (h)
    }
}


App. js

Module lexer from 'compiler/lexicalhandler ';
Module stdlib from '@ std ';

//... Scan (using line [0])...

ES Harmony has many exciting new features to simplify application development and handle dependency management issues. However, so far, no new specifications have been formed and cannot be supported by many browsers. Currently, the best way to use the Harmony syntax is through transpiler, such as Google's Traceur or Esprima. Before the release of the new specifications, we chose AMD and CommonJS to be more secure.


Conclusion

This article discusses several modular programming methods, each of which has its own advantages and disadvantages and has its own application scenarios. We hope to select an appropriate mode in future programming practices to continuously improve code readability, maintainability, and scalability.



In-depth understanding of the JavaScript module mode



The module mode is a common JavaScript encoding mode. This is a general understanding, but some advanced applications have not received much attention. In this article, I will review my basic knowledge, browse some good advanced skills, and even I think it is a native basis.

Basic knowledge

First, let's give a brief overview of the model. Three years ago, Eric Miraglia (YUI)'s blog post made model models well known. If you are familiar with the model mode, you can directly read "advanced mode ".

Anonymous closure

This is the foundation for everything to become possible and the best feature of JavaScript. We will simply create an anonymous function and execute it immediately. The internal code of all functions is in the closure. It provides the private and state of the entire application lifecycle.

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

Note the () around the anonymous function (). This is a language requirement. The keyword function is generally considered as a function declaration, including () as a function expression.

Introduce global

JavaScript has a feature called implicit global. When a variable name is used, the interpreter looks for the variable declaration from the scope to the back. If not found, the variable is assumed to be global (which can be called globally later ). If it is allocated for use, it is created globally when it does not exist. This means that it is easy to use global variables in anonymous functions. Unfortunately, this will make the code difficult to manage, and it is not easy to distinguish (for humans) which variable is global in the file.

Fortunately, there is another good option for anonymous functions. The global variables are passed to the anonymous function as parameters. Introducing them into our code is clearer and faster than using a hidden global. The following is an example:

(Function ($, YAHOO ){
// The current domain has the permission to access global jQuery ($) and YAHOO
} (JQuery, YAHOO ));

Module egress

Sometimes you not only want to use global variables, but you need to declare them first (global calls of modules ). We use the returned values of anonymous functions to output them easily. This completes the basic module mode. The following is a complete example:

Var MODULE = (function (){
Var my = {},
PrivateVariable = 1;
     
Function privateMethod (){
//...
    }
     
My. moduleProperty = 1;
My. moduleMethod = function (){
//...
};
     
Return my;
}());

Note: We declare a global MODULE with two public attributes: method MODULE. moduleMethod and Attribute MODULE. moduleProperty. In addition, the closure of the anonymous function maintains the private internal state. At the same time, we can easily introduce the required global variables and output them to global variables.
 
Advanced Mode

For many users, the above is still insufficient. We can use the following models to create a powerful and scalable structure. Let's use the MODULE to continue one by one.

Expansion

One limitation of the module mode is that the entire module must be in a file. Anyone knows the necessity of separating long code into different files. Fortunately, we have a good way to expand the module. (In the expansion file) first we introduce the module (from the Global), add attributes to it, and then output it. The following is an example of the expansion module:

Var MODULE = (function (my ){
My. anotherMethod = function (){
// The previous MODULE returns the my object as the global output. Therefore, the MODULE parameter of this anonymous function is
};
 
Return my;
} (MODULE ));

We use the var keyword again to maintain consistency, though not necessary. After the code is executed, the MODULE obtains a new public method MODULE. anotherMethod. The expanded file does not affect the private internal status of the module.

Loosely coupled expansion

In the above example, we need to first create a module and then expand it. This is not always necessary. The best operation to improve the performance of JavaScript applications is to load scripts asynchronously. Therefore, we can create flexible modules and load them unsequentially to expand with loose coupling. Each file should have the following structure:

Var MODULE = (function (my ){
// Add capabilities...
     
Return my;
} (MODULE | {}));

In this mode, the var statement is required to mark that it does not exist when it is introduced. This means that you can load all Module files at the same time as LABjs without being blocked.

Tightly coupled expansion

Although the loose coupling is good, there are some limitations on the module. Most importantly, you cannot safely overwrite module attributes (because there is no loading order ). The module attributes defined by other files cannot be used during initialization (but you can run them after initialization ). Tightly coupled expansion means a set of loading sequence, but overwriting is allowed. The following is an example (expanding the MODULE originally defined ):

Var MODULE = (function (my ){
Var old_moduleMethod = my. moduleMethod;
     
My. moduleMethod = function (){
// Method override, has access to old through old_moduleMethod...
};
     
Return my;
} (MODULE ));

The MODULE. moduleMethod we override remains in the private internal state.

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 method may be the least flexible. He can achieve clever combinations, but sacrifices flexibility. As I wrote, the attributes or methods of an object are not copied, but two references of an object. Modifying one will affect others. This may keep the attributes of recursive clone objects fixed, but not the fixed method, except for the eval method. However, I have completely included the module. (In fact, it is a shallow copy ).

Cross-file private status

Splitting a module into several files has a serious defect. Each file has its own private state and has no permission to access the private state of other files. This can be fixed. The following is an example of loosely coupled expansion. Different extended files are kept private:

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;
}, // After the module is loaded, call it to remove the access permission to _ private
_ Unseal = my. _ unseal = my. _ unseal | function (){
My. _ private = _ private;
My. _ seal = _ seal;
My. _ unseal = _ unseal;
}; // Before loading the module, enable access to _ private to expand some of the operations on private content
     
// Permanent access to _ private, _ seal, and _ unseal
     
Return my;
} (MODULE | {}));

Any file can set attributes in the local variable _ private, which takes effect immediately for other extensions (that is, all the extended private states are saved in the _ private variable during initialization, and my. _ private output ). The MODULE is fully loaded. The application calls the MODULE. _ seal () method to stop reading private attributes (Killing my. _ private output ). If the module needs to be expanded later, a private method is provided. Call the MODULE. _ unseal () method before loading the extended file (restore my. _ private, external restore operation permission ). Load and then call seal ().

This mode has been working with me till now, and I have not seen any other places to do this. I think this mode is useful and worth writing.

Sub-module

The last advanced mode is actually the simplest. There are many good ways to create sub-modules. It is the same as creating a parent module:

MODULE. sub = (function (){
Var my = {};
// Multi-level namespace
     
Return my;
}());

Although it is very simple, I 'd like to mention it. The sub-module provides functions of all normal modules, including expansion and private status.


Summary

Most advanced modes can be combined into more useful modes. If I want to propose a design pattern for a complex application, I will combine loose coupling, private status, and sub-modules.

I have not covered performance yet, but I have a small suggestion: The module mode is performance gain. It simplifies a lot and accelerates code downloads. Loose Coupling allows parallel downloads without blocking, which is equivalent to improving the download speed. Initialization may be slower than other methods, but it is worth balancing. As long as it is correctly introduced globally, the running performance will not suffer any losses. It may also speed up the loading of sub-modules due to local variables and fewer references.

Finally, an example is provided to dynamically load the sub-module to the parent module (dynamic creation. The private state is no longer needed here. In fact, it is also very easy to add. This code allows the entire complex code core and its sub-modules to be loaded in parallel completely.

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 you will benefit from it. Please comment and share your thoughts. Now, move on and write better and more modular JavaScript!


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.