preface
Modern front-end development deals with modules all the time. For example, introducing a plug-in in a project, or implementing a JS file for global use of the component. These can all be called modules.
When designing a program structure, it is impossible to put all the code together. A friendlier way of organizing code is to break it up into pieces of code for a specific function, each of which implements a function or a specific purpose, and then combine them together through an interface. That’s the idea of modules.
Modules in JavaScript
As we all know, JavaScript didn’t have modules in its early days. The only way to insert multiple JS files into HTML is through the SRCIpt tag. This approach can lead to a number of problems as projects get bigger and bigger:
- You need to manually maintain the loading order of the JavaScript. There are often many dependencies between scripts, but these relationships are implicit unless you look at the comments one by one (if there are none, then…). Otherwise it is difficult to indicate who depends on whom.
- Name conflict. All content defined by all script files is shared by the global scope. One person development is good, meet with many people cooperation development, that is disaster.
- This can also affect page loading if there are too many scripts. Because script tags require resources to be requested from the server, too many requests can seriously slow down rendering.
And modularization can solve the above problems very well.
What is a module
Modules are JS files loaded in different ways. This different pattern is necessary because it has very different semantics from scripts, with ES6 modules having the following semantics:
- The module runs automatically in strict mode.
- Variables created in the top-level scope of a module are not automatically added to the shared global scope; they then live in their respective top-level scope of the module.
- Module dependencies can be clearly indicated by importing and exporting statements.
These differences represent significant changes in the loading and execution of JS code.
Module development
Since 2009, the JavaScript community has been trying to modularize, and AMD, CommonJs, CMD and other solutions have emerged successively.
Nodejs is the primary practitioner of the CommonJs specification, so it is the most widely used of these solutions. But these are community-generated specifications, not features of the language itself.
By 2015, ECMAScript6.0 formally defined the JavaScript module standard.
CommonJs
In 2009, JavaScript proposed the CommonJs specification, which contains a series of standards including modules, files, IO and console. Nodejs implements a portion of CommonJS and tweaks it. What we’re talking about now is the version of CommonJs in Nodejs, not its original definition.
CommonJs was originally intended only for the server side. Because it defines two main concepts:
- Require function, used for import;
- Module. exports variable, used for exports;
The browser does not support either keyword. Until the community came up with libraries such as Browserify that compiled CommonJS into a syntax that browsers could support. This means that the client code can be written in accordance with the Commonjs standard.
In addition, Nodejs’ NPM package manager allows developers to tap into the community’s excellent code base, or to distribute their code to those who need it. This approach has made CommonJs increasingly popular in front-end development.
The module
The CommonJs specification states that each file is a module. Files imported using require form a module scope of their own, so that variable and function declarations do not contaminate the global scope. All variables and functions are accessible only to the module itself and are not visible to the outside world.
For example:
// foo.js
var name = 'foo';
// bar.js
var name = 'bar';
require('./foo.js');
console.log(name); // bar
Copy the code
Foo.js is loaded in bar.js via the require function. When run, the output is’ bar ‘, indicating that variable declarations in foo.js do not affect bar.js. Each file has its own scope.
export
Exporting is the only way a module exposes itself to the outside world. In CommonJs, the contents of modules can be exported through module.exports.
For example:
module.exports = {
name: 'foo'
}
Copy the code
The CommonJs module has a module object that holds information about the current module. The following objects are defined at the beginning of each module:
var module = {};
// ...
module.exports = {};
Copy the code
CommonJs also supports another export: exports.
exports.name = 'foo'
Copy the code
Implementatively, this code is no different from the module.exports code above; the underlying mechanism is that exports points to Module. exports. CommonJs adds the following code at the beginning of each module by default:
var module = {
exports: {}
}
var exports = module.exports;
Copy the code
Thus, assigning a value to export.name is equivalent to adding a name attribute to the module.exports object.
Exports and module.exports just point to the same object. So an assignment to exports to point to a new object will not work.
For example:
exports = {
name: 'foo'
}
Copy the code
The name attribute is not exported.
The other two methods, they don’t work together. Using module.exports assigns the same thing as pointing to a new object. Previous exports assignments will be invalidated.
The import
CommonJs uses the require function for import operations.
For example:
// foo.js
module.exports = {
sayname: function () {
console.log('foo'); }}; // bar.js var sayname = require('./foo.js').sayname;
sayname(); // foo
Copy the code
Import foo.js in bar.js and call its sayname function.
There are two situations when requiring a module:
- The module is loaded for the first time by require. The module is executed first, then the content is exported.
- Modules have been loaded by require before. The result of the execution is directly exported.
For example:
// foo.js
console.log('running foo.js')
exports.name = 'foo';
// bar.js
var firstname = require('./foo').name;
console.log('firstname:', firstname);
var lastname = require('./foo').name;
console.log('lastname:', lastname);
Copy the code
The output is:
running foo.js
fistname:foo
lastname:foo
The code above requires foo files in two places, but as a result, foo.js is only run once.
The Module object has a loaded property to record whether the module has been loaded. The default value is false. When the module is loaded for the first time, it is assigned true. When the module is loaded again, it checks whether module.loaded is true, and if so, it returns the result without executing the code again.
The require function can accept expressions and use this feature to dynamically specify a module’s load path.
For example:
var path = ['foo.js'.'bar.js'];
path.forEach(name => {
require('/' + name);
})
Copy the code
ES6Module
CommonJs can be said to better solve the problem of modules, but these are only specifications proposed by the community, not the characteristics of the language itself.
By 2015, ECMAScript6.0 formally defined the JavaScript module standard. This is where modules came into the JavaScript language.
The module
Rewrite the CommonJs example with ES6Module.
// foo.js
export default {
sayname: function () {
console.log('foo'); }}; // bar.js import foo from'./foo.js'
foo.sayname(); // foo
Copy the code
ES6Module also treats each file as a module, and each module has its own scope. The difference is the import and export statements. Import and export are also added as reserved keywords in ES6, and ES6Module automatically adopts strict mode.
export
Use the export command in ES6Module to export modules. Export has two forms:
- Named after the export
- The default is derived
1, a module can have multiple named exports, there are two different ways to write:
/ / 1export const name = 'foo';
// 2
const name = 'foo';
export { name }
Copy the code
Variables can be renamed using the AS keyword.
Such as:
const name = 'foo';
export { name as nickname }
Copy the code
There can be only one default export:
export default {
name: 'foo'
}
Copy the code
You can think of export default as output of a variable named default.
The import
ES6Module uses the import syntax to import modules.
For example:
// foo.js
export const name = 'foo';
// bar.js
import { name } from './foo'
console.log(name)
Copy the code
When loading a module with a command export, import is followed by curly braces to enclose the imported variable name, which must be exactly the same as the exported variable name. Importing variables has the effect of declaring them in the current scope and cannot be aligned for modification.
Similar to command exports, you can rename imported variables using the AS keyword.
For example:
// foo.js
export const name = 'foo';
// bar.js
import { name as nickname } from './foo'Console. log(nickname) // Import * as name from'./foo'
console.log(name.name)
Copy the code
Examples of default imports:
// foo.js
export default {
name: 'foo'
}
// bar
import name from './foo'
console.log(name.name)
Copy the code
CommonJs vs. ES6Module
Differences in the handling of module dependencies
The fundamental difference between CommonJs and ES6Module is that the former resolves module dependencies dynamically, while the latter is static.
- Dynamic: module dependencies are established during code runtime;
- Static: module dependencies are established during code compilation;
In CommonJs, when module A loads module B, it executes B’s code and returns its module.exports object as the return value of the require function. The module path of requrie can be specified dynamically, a representation can be passed in, and a module can even be loaded using an if statement. So there is no way to determine explicit dependencies before CommonJs modules are executed, and module import/export takes place at runtime.
ES6Module import and export statements are declarative. The unsupported import path is an expression, and import and export statements must be in the top-level scope of the module. Module dependencies can be analyzed during compilation of ES6 code.
Import differences in module values
When importing a module, for CommonJs you get a copy of the exported value; In ES6Module it is a dynamic mapping of values, and the mapping is read-only.
For example:
// foo
var count = 0;
module.exports = {
count: count,
add: function (a, b) {
count++;
return a+b;
}
}
// bar
var count = require('./foo').count;
var add = require('./foo').add; console.log(count); / / 0 add (2, 3); console.log(count); // 0 count += 1; console.log(count); // 1(copy value can be changed)Copy the code
Count in bar is a copy of the value of count in foo, so when you call add, you change the value of count in foo, but you don’t change the imported value in bar.
Copy values, on the other hand, can be changed.
Rewrite using ES6Module
// foo
let count = 0;
const add = function (a,b) {
count++;
return a+b;
}
export { count, add }
// bar
import { count, add } from './foo'; console.log(count); / / 0 add (2, 3); console.log(count); // count++; // error count isread-only
Copy the code
A mapping relationship can be understood as a mirror in which the original things can be observed in real time, but the image in the mirror cannot be manipulated.
The difference between cyclic dependencies
Examples of cyclic dependencies in CommonJs:
// foo
const bar = require('./bar')
console.log('From bar:', bar);
module.exports = 'foo';
// bar
const foo = require('./foo');
console.log('From foo:', foo);
module.exports = 'bar; // index require('./foo')
Copy the code
Output on the console:
From Foo: () from bar: barCopy the code
First of all, to sort out the implementation process:
- Foo is introduced in the index file, and the code in foo is executed;
- Foo’s first sentence imports bar, which means foo doesn’t execute down, but inside the bar.
- In bar, foo is introduced, where circular dependencies are created. Foo is not executed again, but the return value, module.exports, is exported directly. But since foo is unfinished, the exported value is the default empty object, so when bar executes to console.log, it prints an empty object.
- When bar completes execution, Foo continues down until the process ends.
Rewrite the above example using ES6Module:
// foo
import bar from './bar';
console.log('From bar:', bar);
export default 'foo'
// bar
import foo from './foo'
console.log('From foo:', foo);
export default 'bar'
// index
import foo from './foo'
Copy the code
Result: from foo: undefined from bar: bar
You can’t get the correct export value for foo in bar, but instead of CommonJS exporting an empty object by default, you get undefined.
At the end
Modules are an important concept in programming, and I hope this gives you an idea of what front-end modules are. Search the official website or books for detailed usage.
For more articles, please go to Github, if you like, please click star, which is also a kind of encouragement to the author.