preface
With the development of front-end technology, modular development has become a common solution of front-end development.
This paper mainly introduces the concept of modularization, origin, advantages and common modular specifications in front-end development.
First, understanding modularity
Module concepts?
For a complex program, it is encapsulated into several file blocks according to certain specifications, and each piece exposes some interfaces to the outside, but the internal data of the block is private, and the communication between blocks is carried out through exposed interfaces. This process is called modularization.
Basic characteristics of a module:
- Code encapsulation to avoid global contamination
- Have unique identity
- Expose some data or API methods for external use
- The module is convenient and quick to use
Why modularity
The origin of modularization, need to start from the early development mode.
None Encapsulation None module
Early JS was just a browser script to implement some simple interactions.
Because of the small amount of code, you can simply put the code in the
<script>
document.getElementById('hello').onClick = function () {
alert('hello');
}
document.getElementById('submit-btn').onClick = function () {
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
if(! username) { alert('Please enter a user name');
return;
}
if(! password) { alert('Please enter your password');
return;
}
// Submit the form
console.log('Submit form');
}
</script>
Copy the code
With the development of front-end technology and the wide application of JS, the amount of code is increasing day by day. The original code stack has a number of drawbacks:
- Generating a lot of duplicate code, copying and pasting the same functionality here and there
- Logical confusion is not conducive to reading and maintenance
- It’s very buggy.
Some of the functionality is then wrapped up in functions and executed by calling them.
Encapsulate global function functions
Call different functions by encapsulating them into different functions
// The setValue function is defined
function setValue(name){
document.getElementById('username').value = name;
}
// The getValue function is defined
function getValue(){
return document.getElementById('username').value;
}
// The setValue function is defined
function setValue(name){
document.getElementById('phone').value = name;
}
// The getValue function is defined
function getValue(){
return document.getElementById('phone').value;
}
Copy the code
Zhang SAN defined setValue and getValue methods to achieve their own functions, and there was no problem under the test
The next day, Li Si added functions and defined setValue and getValue methods. He tested his own functions and found no problem
On the third day, the test gave Sam a bug.
So, this approach also has its drawbacks: it can pollute the global namespace, cause naming conflicts, and have no apparent dependencies between module members
The namespace
Declare a namespace object that encapsulates data and methods into the object, reducing global variables and resolving naming conflicts
const tool = {
id: 'tool_1'.type: 'input'.value: '123'.getType() {
console.log(`type-The ${this.type}`);
return this.type;
}
getValue() {
console.log(`value-The ${this.value}`);
return this.value;
},
}
tool.type = 'checkbox' // Modify the data inside the module directly
tool.getType() // 'checkbox'
Copy the code
This way of writing exposes all module members, and the internal state can be overwritten externally, leading to data security issues.
Perform functions and dependency injection immediately
Encapsulate data and methods inside a function, exposing the API interface by adding properties to the window
/ / tool. Js file
(function(window, $) {
let id = '#tool_1';
let type = 'input';
let value = '123';
let count = 0;
/ / function
function getType() {
console.log(`type-The ${this.type}`);
return type;
}
function getValue() {
console.log(`value-${$(id).val()}`);
return $(id).val();
}
function setValue(val) {
value = val;
}
function increase() {
count++;
}
// Private methods
function resetValue() {
value = '123';
}
// Private methods
function resetCount() {
count = 0;
}
function resetHandler() {
console.log('resetHandler');
resetValue();
resetCount();
}
// Method of exposure
window.tool = { getType, getValue, setValue, increase, resetHandler }
})(window, jQuery)
Copy the code
You must import JS in the correct order (index.html file)
<script type="text/javascript" src="Jquery - 1.7.2. Js"></script>
<script type="text/javascript" src="tool.js"></script>
<script type="text/javascript">
tool.setValue('567');
</script>
Copy the code
The above example uses jquery to get the value of the input box, so you must first import the jquery library as a parameter.
Limitations of the original development plan:
Script tags + function encapsulation + namespaces + immediate execution of functions have significant limitations
- 1. Global space pollution
- 2. Manual management of dependencies is required, which does not have scalability
- 3. Problems with repeated loading and circular references
- 3. Load on demand cannot be realized
- 4. Generate a large number of HTTP requests
Advantages of modularity
- Avoiding naming conflicts (reducing namespace pollution)
- Function separation, loading on demand
- reusability
- maintainability
Next, commonJS, AMD, CMD, ES6 specifications commonly used in development are introduced.
Second, modular specification
CommonJS
CommonJS overview
A file represents a module and has its own scope. Variables, functions, and classes defined in modules are private, exposing variables and API methods for external use.
Node adopts the CommonJS module specification, but it is not implemented in full accordance with the CommonJS specification. Instead, it makes a certain choice of the module specification and adds a few features it needs.
CommonJS characteristics
-
All code runs in the module scope and does not pollute the global scope.
-
Modules can be loaded multiple times, but only run once on the first load, and then the results are cached and read directly from the cache when they are loaded later. For the module to run again, the cache must be cleared.
-
The order in which modules are loaded, in the order they appear in the code.
-
Synchronous loading at runtime
CommonJS basic usage
Module definition and export
// moduleA.js
let count = 0;
module.exports.increase = () = > {
count++;
};
module.exports.getValue = () = > {
return count;
}
Copy the code
Module is introduced into
The require command is used to load the module file. If no specified module is found, an error message is displayed.
You can import modules in one file and export another.
// moduleB.js
// If the parameter string begins with "./ "or".. / ", it indicates that the file is loaded in a relative path
const { getValue, increase } = require('./moduleA');
increase();
let count = getValue();
console.log(count);
module.exports.add = (val) = > {
return val + count;
}
Copy the code
Module identifier
ModuleName: moduleName: moduleName: moduleName: moduleName: moduleName: moduleName
- It has to be a string
- If the module is a third-party module
moduleName
For the module name - If it is a custom module,
moduleName
Is the module file path; It can be a relative path or an absolute path
- If the module is a third-party module
- You can omit the suffix
The CommonJS module specification limits variables and methods to private scopes. Each module has its own space and does not interfere with each other. It also supports import and export to connect upstream and downstream modules.
CommonJS module loading mechanism
In addition to its function scope, a module has another module scope in its outermost layer. Module represents this module and is an object. Exports is the attribute of module and is the interface exposed externally.
Require is also used in the context of this module to import external modules, which load the module.exports property of other modules.
Next, analyze the general loading process of CommonJS module
function loadModule(filename, module.require, __filename, __dirname) {
const wrappedSrc = `(function (module, exports, require, __filename, __dirname) {
${fs.readFileSync(filename, "utf8")}})(module, module.exports, require, __filename, __dirname) ';
eval(wrappedSrc);
}
Copy the code
This is just an overview of the loading process. Many boundary and security issues are not considered, such as:
Here we are simply using eval for our JS code, which actually has a lot of security issues, so the actual code should be implemented using VM.
There are two additional arguments in the source code: __filename and __dirname, which is why we can use them directly when writing code.
The require implementation
function require(moduleName) {
// Resolve the complete module path to an absolute path string
const id = require.resolve(moduleName);
// Check whether the id path is already cached in require.cache
if (require.cache[id]) {
return require.cache[id].exports;
}
// module metadata
const module = {
exports: {},
id,
};
// After a new module is loaded, add the module path to the cache so that the cache can be read directly through the id path
require.cache[id] = module;
// Load the module
// loadModule(id, module, require);
// Directly integrate the loadModule method above
(function (filename, module.require) {(function (module.exports.require) {
fs.readFileSync(filename, "utf8"); }) (module.module.exports, require);
})(id, module.require);
/ / return the module exports
return module.exports;
}
require.cache = {};
require.resolve = (moduleName) = > {
/* Parses the complete module path to get an absolute path string */
return 'Absolute path string';
};
Copy the code
Exports object is passed to an internal self-executing function. The module mounts data or methods to the module.exports object and returns the module.exports object.
For example, the previous moduleA.js and moduleB.js modules:
-
The moduleA module mounts the increase and getValue methods to the module.exports object of the context
// moduleA.js let count = 0; module.exports.increase = () = > { count++; }; module.exports.getValue = () = > { return count; } Copy the code
-
ModuleB requires moduleA and returns the module.exports object that mounts increase and getValue methods. This object is structurally assigned and eventually received by the increase and getValue variables in moduleB.
// moduleB.js const { getValue, increase } = require('./moduleA'); / / equivalent to the // let m = require('./moduleA'); // const getValue = m.getValue; // const increase = m.increase; increase(); let count = getValue(); console.log(count); module.exports.add = (val) = > { return val + count; } Copy the code
Require. resolve loads the policy
We already know that resolve is the method property of require. What it does is it completes the path passed in to get an absolute path string.
function require(moduleName) {.../ / return the module exports
return module.exports;
}
require.resolve = (moduleName) = > {
/* Parses the complete module path to get an absolute path string */
return 'Absolute path string';
};
Copy the code
In actual projects, we often use the following methods:
- Import your own module file
- Import the core modules provided by NodeJS
- The import
node_modules
Package module in
We can briefly summarize the loading strategy:
- First determine whether it is a core module, and then search the list of modules provided by NodeJS itself. If so, return it directly
- To determine parameters
moduleName
Whether or not to. /
or../
At the beginning, if yes, the unified conversion to the absolute path to load and return - If the first two steps are not found, consider it a package module and go to the nearest one
node_moudles
Search in directory
As moduleName can omit suffix names, it should follow a rule for judging suffix names. The priority order of judging different suffix names is as follows:
- if
moduleName
If it is a file with a suffix, return it directly. - if
moduleName
If yes, load the path in the following order- moduleName.js
- moduleName.json
- moduleName.node
- moduleName/index.js
- moduleName/index.json
- moduleName/index.node
- If the package module is loaded, it will follow the package module
package.json
Of the filemain
The value of the field property to load
Nodejs modular implementation
Nodejs module is not implemented in accordance with CommonJS completely, and has made a trade-off, adding some of its own features.
A file in Nodejs is a module: module, and a module is an instance of a Module
Nodejs Module constructor:
function Module(id.parent){
this.id = id;
this.exports = {};
this.parent = parent;
if(parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
// instantiate a module
var module = new Module(filename, parent);
Copy the code
Where ID is the module ID, exports is the API interface exposed by the module, parent is the parent module, loaded indicates whether the module is loaded.
AMD
AMD(Asynchronous Module Definition) Mainly used in browsers, because the specification is not supported by native JS, the use of AMD specification for development needs to introduce third-party library functions, which is the popular RequireJS RequireJS is an implementation of AMD specification. In fact, IT can also be said that AMD is the standardized output of RequireJS module definition in the promotion process.
AMD is an asynchronous module loading specification. Its main difference from CommonJS is asynchronous loading, allowing you to specify callback functions. Even if the require module is not acquired during module loading, it will not affect the subsequent code execution.
Node.js is mainly used for server programming, module files usually already exist on the local hard disk, so it is fast to load, and asynchronous loading is not considered, so the CommonJS specification is more applicable. However, the browser environment, from the server to download the module file, then must use asynchronous loading, so the browser generally uses AMD specifications. In addition, the AMD specification was implemented earlier on the browser side than the CommonJS specification.
AMD specification basic syntax
RequireJS defines a define function that defines modules
Grammar:
define([id], [dependencies], factory)
Copy the code
Parameters:
- Id: Optional, a string that defines the module id. If no argument is provided, the default is the file name
- Dependencies: An optional array of strings on which the current module depends
- Factory: Required, factory method that initializes the function or object that the module needs to execute. If is a function, it is executed only once. If it is an object, this object is used as the output value of the module
Module definition and export
-
Define independent modules with no dependencies
// module1.js define({ increase: function() {}, getValue: function() {}});/ / or define(function(){ return { increase: function() {}, getValue: function() {},}});Copy the code
-
Define modules with dependencies
// module2.js define(['jQuery'.'tool'].function($, tool){ return { clone: $.extend, getType: function() { returntool.getType(); }}});Copy the code
-
Defining named modules
define('module1'['jQuery'.'tool'].function($, tool){ return { clone: $.extend, getType: function() { returntool.getType(); }}});Copy the code
Introducing usage modules
require(['module1'.'module2'].function(m1, m2){
m1.getValue();
m2.getType();
})
Copy the code
The require() function loads dependent modules asynchronously so that the browser does not become unresponsive
AMD specification and CommonJS specification comparison
CommonJS
Generally used on the server,AMD
Generally used in browser clientsCommonJS
andAMD
It’s all run time loading
What is run-time loading?
CommonJS
andAMD
Modules can only determine dependencies between modules at run timerequire
When a module is executed, the module returns an object that is loaded as a whole
Summary: THE AMD module defines methods that clearly display dependencies without polluting the global environment. AMD mode can be used in a browser environment, allowing modules to be loaded asynchronously or dynamically as needed.
CMD
CMD(Common Module Definition), it solves the same problem as AMD specification, but in the Module Definition way and Module loading time is different, CMD also needs to introduce additional third-party library files, SeaJS CMD is the normalized output of the module definition in the SeaJS promotion process.
CMD regulates the basic syntax
Define is a global function that defines a module
Grammar:
define([id], [dependencies], factory)
Copy the code
Parameters:
- Id: Optional, a string that defines the module id. If no argument is provided, the default is the file name
- Dependencies: An optional array of strings that the current module depends on
- Factory: Required, factory method that initializes the function or object that the module needs to execute. If is a function, it is executed only once. If it is an object, this object is used as the output value of the module
Module definition and export
In addition to adding members to exports objects, you can also use return to provide interfaces directly to exports
-
Define modules that have no dependencies
define(function(require.exports.module) { module.exports = { count: 1.increase: function() {}, getValue: function() {}}; })/ / or define(function(require.exports.module) { return { count: 1.increase: function() {}, getValue: function() {}}; })Copy the code
-
Define modules with dependencies
define(function(require.exports.module){ // Import dependent modules (synchronization) const module1 = require('./module1'); // Introduce dependency modules (asynchronous) require.async('./tool'.function (tool) { tool.getType(); }) // Expose the module module.exports = { value: 1 }; }) Copy the code
Introducing usage modules
define(function (require) {
var m1 = require('./module1');
var m2 = require('./module2');
m1.getValue();
m2.getType();
})
Copy the code
The CMD specification is designed specifically for the browser side. Modules are loaded asynchronously and executed only when they are in use. The CMD specification incorporates the features of the CommonJS and AMD specifications.
ES6 modular
The ES6 module is designed to be as static as possible, so that the dependencies of the module, as well as imported and exported variables, can be determined at compile time. Both CommonJS and AMD modules can only be determined at run time.
ES6 modular syntax
The export command is used to expose the external interface of a module, and the import command is used to import other modules.
Module definition and export
// moduleA.js
let count = 0;
export const increase = () = > {
count++;
};
export const getValue = () = > {
return count;
}
Copy the code
Module is introduced into
// moduleB.js
import { getValue, increase } from './moduleA.js';
increase();
let count = getValue();
console.log(count);
export function add(val) {
return val + count;
}
Copy the code
You can alias variables or methods when importing modules. You need to use the AS keyword to define the alias
// moduleB.js
import { getValue as getCountValue, increase as increaseHandler } from './moduleA.js';
increaseHandler();
let count = getCountValue();
console.log(count);
Copy the code
As shown in the preceding example, when using the import command, you need to know the name of the variable or function to be loaded; otherwise, the command cannot be loaded.
To make it easier for users to load the module without reading the documentation, use the export default command to specify the default output for the module.
After a module is exported by default, the import command can specify any name for the anonymous function when it is loaded by other modules.
// add.js
export default function (a, b) {
return a + b;
}
// demo.js
import add from './add';
console.log(add(1.2)); / / 3
Copy the code
If you want to import a default method along with other variables and methods in an import statement, you can write it as follows.
// moduleA.js
let count = 0;
export const increase = () = > {
count++;
};
export const getValue = () = > {
return count;
}
export default {
a: 1
}
// moduleB.js
import _, { getValue, increase } from './moduleA.js';
increase();
let count = getValue();
console.log(count);
console.log(_);
Copy the code
This usage can be seen throughout the React project
import React, { useState } from 'react';
function Hello() {
let [ count, setCount ] = useState(0);
return (
<div>
<p>You click { count } times</p>
<button onClick={()= >SetCount (count + 1)}> setCount</button>
</div>)}Copy the code
The overall import
In addition to specifying that certain output values are loaded, you can also use global loading, where you specify an object with an asterisk (*) on which all output values are loaded.
// moduleB.js
// import { getValue, increase } from './moduleA.js';
import * as handler from './moduleA.js';
handler.increase();
let count = handler.getValue();
console.log(count);
Copy the code
Other situations
The import command is promoted to the top of the module and executed first.
foo();
import { foo } from './foo.js';
Copy the code
The above code does not report an error because import is executed before foo is called. The essence of this behavior is that the import command is executed at compile time, before the code runs.
Because import is executed statically, you cannot use expressions and variables, which are syntactic structures that are only available at run time.
import { 'f' + 'oo' } from './foo'; / / an error
let module = './foo';
import { foo } from module; / / an error
/ / an error
if (x === 1) {
import { foo } from './foo1';
} else {
import { foo } from './foo2';
}
Copy the code
All three of the above methods report errors because they use expressions, variables, and if constructs. In the static analysis phase, none of these grammars can be evaluated.
The import statement executes the loaded module, so it can be written as follows.
import './initData';
Copy the code
Initdata.js initializes data by executing its own methods, without exporting variables and methods. So just import the module and initialize it.
Differences between ES6 modules and CommonJS modules
There are two important differences:
- 1, CommonJS module is run time load, ES6 module is compile time output interface.
- 2, CommonJS module outputs a copy of the value, ES6 module outputs a reference to the value.
CommonJS loads an object (that is, the module.exports property) that is generated only after the script runs. An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase.
ES6 modules operate differently from CommonJS. When the JS engine statically analyzes a script, it generates a read-only reference to the module load command import. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference. In other words, the IMPORT of ES6 is a bit like the “symbolic link” of Unix systems, where the original value changes and the import load value changes with it. Therefore, ES6 modules are referenced dynamically and do not cache values. Variables in modules are bound to the module in which they are located.
summary
Because ES6 modules are loaded at compile time, static analysis is possible. With it, you can further expand JavaScript syntax to include features like type system that can only be implemented with static analysis.
Third, summary
-
The CommonJS specification is mainly used for server-side programming, loading modules is synchronous; Synchronous loading would block in a browser environment, so it would not fit this specification, hence the AMD and CMD specifications.
-
The AMD specification loads modules asynchronously in the browser environment, and multiple modules can be loaded in parallel. However, THE DEVELOPMENT cost of AMD specifications is high, and the code is difficult to read and write.
-
The CMD specification is similar to the AMD specification in that it is used for browser programming, relies on proximity, executes lazily, and can be easily run in Node.js.
-
ES6 in the language standard level, the realization of module function, and the implementation is quite simple, can completely replace CommonJS and AMD specifications, become a common browser and server module solution.