preface

The initial Web side interaction is still very simple and does not require a lot of JS to implement. With the development of The Times, users have put forward higher and higher requirements for the performance of Web browsers, and more and more browsers have undertaken more and more interactions, which can no longer be solved by JS with a few words. As a result, the front-end code is expanding day by day, and the interdependence between JS will become more and more. At this point, some specification is needed to manage the dependencies between JS. This article focuses on what modularity is, why it is needed, and the current popular modularity specifications: AMD, CMD, CommonJs, ES6.

What is modularity

The way to understand what modularity is is to understand what a module is. Okay? Module: a collection that can be named independently and can perform certain functions independently. Therefore, in JS, it can be understood that modules are independent JS files that can achieve specific functions. Modularization: it can be simply understood as the original heavy complex js file according to the function or according to the module split into a separate JS file, and then throw out some methods in each JS file, to other JS files to reference and rely on.

Why modularity is needed

A modular process

The global function mode encapsulates different functions into different functions. Disadvantages: Pollution of the global namespace, easy to cause naming conflicts, can not see dependencies between modules. Data is insecure (external functions can modify data within modules) and dependencies between modules are invisible

const module = { data:1, getData(){console.log(this.data)} } module.data = 2; // This directly modifies the data inside the moduleCopy the code

3, IIFE mode: anonymous function self-call (closure) function: solve data security, data is private, external can only call exposed information disadvantages: need to bind to a global variable, such as window exposed, this will also have the problem of naming conflicts. Disadvantages: when introducing JS, you need to pay attention to the order of introduction, and there will be disadvantages when relying on a lot of time

Function (window){let data = 'this is IIFE mode '; getData(){ console.log(data); } window.module = {getData}})(window) {function(window,$){let data = 'this is IIFE mode '; getData(){ console.log(data); $('body').css('background', 'red') } window.module = { getData } })(window,jQuery); <script type="text/javascript" SRC ="jquery-1.10.1.js"></script> <script type="text/javascript" SRC ="module.js"></script> <script type="text/javascript"> mymodule.foo () </script> Too many requests // 2. Dependency ambiguity: it is not clear what the relationship is directly, and it is easy to make mistakes because of the order of introduction // 3. Difficult to maintainCopy the code

From the above development process, although modularity is not so perfect, it is not difficult to find the advantages of modularity:

Two, modular advantages:

  • Avoid naming conflicts
  • Better separation of modules, load on demand
  • High reusability
  • High maintenance

Module specification

AMD

AMD(Asynchronous Module Definition): Asynchronous Module Definition. The module is loaded asynchronously, and the loading of the module does not affect the execution of subsequent statements. All statements that depend on this module are defined in a callback function that will not run until the load is complete. When the browser environment loads modules from the server side, it must be in asynchronous mode, so the browser side generally adopts the AMD specification. RequireJs is the best practice of the AMD specification, so we will introduce it when we use the AMD specification.

AMD syntax:

Define is used to define modules; Require is used to load modules, and usually AMD frameworks use the REQUIRE method as an entry point for dependency analysis and sequential loading. AMD relies on front-loading, which means that an array of modules passed into a method in DEFINE or require is downloaded and executed at the start

//define module // Define (['module1','module2'],function(m1,m2){// If a module is introduced but not used, the contents of the module will also return module; }) // require(['module1','module2'],function(m1,m2){// use m1,m2 // If module is introduced but not used, the contents of module will be executed})Copy the code
AMD specific use process
  1. The introduction of requirejs

A. requirejs: requirejs B. github: requirejs C. Or directly with the require of js links in the index. In the HTML reference (only suggest when learning the use) : requirejs.org/docs/releas… 2. Define modules

//module1.js define(function(){ const msg='module1'; return { msg } }) //module2.js define(function(){ const msg="module2"; return { msg } }) //module3.js define(['module2'],function(m2) { const msg="module3"; const msg2 = m2.msg return { msg, msg2 }; }); Config ({paths:{jquery:'jquery' // for importing third-party libraries, this is the path of the file; }}) require(['module1','module3','jquery'],function(m1,m3,$){console.log(m1,'module1'); function(m1,m3,$){console.log(m1,'module1'); console.log(m3,'module3'); Console. log($,' third-party library '); }); Console. log(' module load '); // Prints firstCopy the code
  1. The introduction of the index.html
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, > <title>Document</title> </head> <body> <! --data-main: file entry --> <script data-main="main" SRC = "https://requirejs.org/docs/release/2.3.6/minified/require.js" > < / script > < / body > < / HTML >Copy the code
  1. The output

CMD

CMD(Common Module Definition) : Common Module Definition. For the browser side, is in addition to AMD another module organization specification. A combination of AMD and CommonJs(more on that later) features. Also load modules asynchronously. Different from AMD: AMD is a reliance on front-loading, while CMD is a reliance on nearby, delayed execution. Dependency precedes dependency nearby and delays execution

// dependency prefixes: AMD require(['module1','module2'],function(m1,m2){// Delay execution define(funciton(require){const module1 = require('./module1'); }}}}}}Copy the code
How to use CMD
  1. The introduction of sea. Js

A. website: seajs. Making. IO/seajs/docs /… B. github: github.com/seajs/seajs 2. The module definition

//module1.js define(function(require,exports) {const MSG =' this is module1 '; // const module2 = require('./module2'); exports.msg = msg; // exports.module2 = module2; // exports.module2 = module2; Exports}); // exports}); // exports}); //module2.js define(function(require,exports,module) {const MSG =" module2 "; Module. Exports = {// module. Exports}}); //module3.js define(function(require,exports,module) {const MSG =" module3 "; const module2 = require('./module2'); Module. exports = {// module. Exports, {MSG, module2}}); //main.js define(function(require, exports,module) { const module1 = require('./module1'); const module3 = require('./module3'); console.log(module1); console.log(module3); }); Console. log(' module load ');Copy the code
  1. Introduced the index.html
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial-scale =1.0"> <title>Document</title> </head> <body> <script SRC =" sea-.js "></script> <script> seajs.use('main'); </script> </body> </html>Copy the code
  1. The output

CommonJS

Node applications are composed of modules that follow the CommonJS module specification. Each file is a module with its own scope. Variables, functions, and classes defined in one file are private and invisible to other files. On the server side, modules are loaded synchronously at runtime. On the browser side, modules need to be compiled and packaged in advance. CommonJs has four variables that are important after all: Module, require, exports, global

CommonJs features:
  1. All code runs in the module scope and does not pollute the global scope; If you want multiple modules to share a variable, add an attribute to global (not recommended).
//module.js global.data = 'Shared variables '; Let x = {a:5} let b=0; const add = function(val){ x.a+=x.a; b=++b; } module.exports.x=x; // If x, b is the module's private variable, module.exports.add = add; // If x, b is the module's private variable, module.exports.add = add;Copy the code
  1. Modules can be loaded multiple times, but only when they are loaded for the first time. Subsequent loads are cached results. If you want to load again, you need to clear the cache, or expose a function, and execute the exposed function after loading
// test1.js let MSG =" test cache "; console.log(msg); exports.msg=msg; // test2.js const msg = require('./test1'); const msg2 = require('./test1'); // The output above is: The test cache // will find that the result is printed only once, proving that it is executed only on the first load, Delete require.cache[moduleName] // moduleName must be an absolute path // Delete all module caches Object.keys(require.cache).forEach(function(key){ delete require.cache[key] })Copy the code
  1. Modules are loaded in the order written in the code. Modules are loaded synchronously.
  2. The imported value is actually a copy (shallow copy) of the output value. That is, once the value is printed, changes in the module do not affect the value change (except for reference types).
// example.js let x= { a:5 }; let b=0; const add = function(val){ x.a+=x.a; b=++b; // return x.a++; } module.exports.x = x; module.exports.b=b; module.exports.add = add; // main.js const example = require('./example'); const add = require('./example').add; example.add(); Console. log(example.x) // {a:10} console.log(example.b) // b:0 CommonJs syntax // module.exports // import modules Require (module path) // Module path can be written in various ways, and will be added laterCopy the code

module.exports&&exports module.exports: Inside each module, module represents the current module. Module is an object that exposes the property of exports. Loading a module is equivalent to loading the property of module.exports: in fact, it is a reference to module.exports. For ease of use, Node creates an exports variable for each module, which points to module.exports. So the following two approaches are wrong.

// exports=' MSG '; Exports. MSG =' MSG '; // exports. exports. module = exports; module.exports = 'Hello world'; // the MSG above is not exposed because module.exports is reassigned; That's when you expose "Hello World."Copy the code

ES6

ES Modules (ESM) is the official standardized module system for JavaScript. The idea behind the ES6 module design is to be as static as possible, so that the module dependencies, as well as the input and output variables, are known at compile time. There are two main commands: export and import. Export is used to expose interfaces and import is used to import other modules.

Features of ES6 module:
  • Strict mode: ES6 modules automatically adopt strict mode
  • Import read-only Property: The import property is read-only and cannot be assigned, similar to const property
  • Export /import promotion: import/export must be at the top of the module, not in scope; Secondly, import/export within the module will be promoted to the top of the module, which is completed during compilation
  • Compatible with node running environment
  • ES Modules outputs references to values, interfaces are dynamically bound, and CommonJS outputs copies of values
// let x= {a:5}; // let x= {a:5}; let b=0; const add = function(val){ x.a+=x.a; b=++b; } export {x,b,add} // main.mjs import { x,b, add} from './example.mjs'; add(); console.log(x,b); // {a:10} 1Copy the code

Export &&import Usage export: used to expose interfaces import: Used to import external interfaces

//export export const x = 1; Export const y = {a:1} export const add = function(){console.log(123)} //export const x=1; const y={a:1}; const add = function(){console.log(123)}; Import {x,y,add} from './exmaple'; import {x,y,add} from './exmaple'; Import * as moduleName from './exmaple'; // Use moduleName. X when usedCopy the code
// We can also use export default to expose interfaces. There can be more export in a module, but only one const x=1 can be export default. const y={a:1}; Export default {x,y} // import import moduleName from './exmaple'; ModuleName. XCopy the code

conclusion

  • AMD: Load modules asynchronously, allowing callback functions to be specified. The AMD specification is front-dependent. The general browser side will use AMD specifications. But development costs are high, and code reading and writing are difficult.
  • CMD: load modules asynchronously. The CMD specification is dependent on proximity, lazy loading. It is also used on the browser side.
  • CommonJs: synchronously loads modules, usually used on the server side. The exposed interface is a copy of the value
  • ES6: Simple implementation. The exposed interface is a reference to a value. It can be used on both the browser side and the server side.

Afterword.

Modularity changes from time to time, so that the system modularity has become better and better, but the response also caused some problems. For example, when using modules, it is found that some modules are introduced but not really used, which leads to the redundancy of the code and some unnecessary code. These modules are sometimes hard to see through inspection, so think about how to remove them quickly. That’s where Tree Shaking comes in. How do you make it easy to develop code and then have high compatibility in production? That’s where Babel comes in. Or how to preprocess modules, when Webpack came along… When a solution appears, continue to learn…

Refer to the article

Front-end Modularization — CommonJS,CMD, AMD, UMD and ESM Front-end modularization (Full version)