Front-end modularization is the cornerstone of front-end engineering. At present, the use of modules is ubiquitous in the big front-end era, and looking to the future, the form of import and export proposed in ES6 seems to unify the modular loading of front and back ends.
First, modular overview
The idea of modularity is to separate different JS files and expose only the other modules required by the current module.
Before studying the modularity of ES6, let’s review the modularity before appearing. There are three common specification definitions: CommonJS, AMD, CMD.
Their characteristics and differences are as follows:
-
CommonJS works on the server side, written as:
var clock = require('clock.js') clock.start(); Copy the code
In other words, it is a synchronous operation. This also leads to CommonJS being widely used in the server rather than the client. (The server reads modules on the local disk, which is fast to load. If it is on the client side, it is easy to appear ‘suspended animation’) so can use asynchronous loading module?
-
Asynchronous Module Definition (AMD) is an Asynchronous loading Module. It is used in browsers (requireJs applies this specification) and is written as follows:
require([module],callback); // eg require(['clock.js'].function(clock){ clock.start(); }) Copy the code
While asynchronous loading is implemented to avoid the browser’s “false death” problem, there is a disadvantage: writing all dependencies out in a non-logical order in the first place. Is it possible to use CommonJS before require, and then support asynchronous load after execution?
-
CMD (Common Module Definition) requires (seaJS preferred specification) when used nearby, written as:
define(function(require,exports,module){ var clock = require('clock.js'); clock.start(); }) Copy the code
The difference between AMD and CMD is that the execution timing of dependent modules is different, rather than the loading process is different. Both are asynchronous loading modules.
AMD depends on the front, JS can be easily clear dependency module has what, load immediately;
CMD to rely on, developers can depend upon the require when in use, but for js processor, need treatment for string parsing the code again to know that rely on which modules, namely sacrificing performance for the convenience of development, although the short time to actually parsing can be ignored, but also has a lot of people criticized CMD to this point.
The modular design of ES6 is designed to be as static as possible so that module dependencies can be determined at compile time.
Compare CommonJS and ES6 modules:
// CommonJS
let { start, exists, readFile } = require('fs')
/ / equivalent to
// let _fs = require('fs')
// let start = _fs.start, exists = _fs.exists, readFile = _fs.readFile
// ES6
import { start, exists, readFile } from 'fs'
Copy the code
In the example above, CommonJS essentially loads the fs module as a whole to generate a _FS object, and then reads three methods from the object, called “runtime load.” The ES6 module loads three methods, called “load at compile time”
Second, ES6 modular syntax specification
Strict mode
Strict mode is automatically adopted in ES6 modules. Requirements:
- Variables must be declared first
- Function parameters cannot have attributes of the same name
- You can’t use
with
- Assign a read-only attribute,
delete
Attributes cannot be deleted - Non-deletable variable
delete prop
, can only delete attributesdelete global[prop]
eval
No more variables are introduced into the outer scopeeval
andarguments
Cannot be reassignedarguments
Does not automatically react to function parameter changes- ban
this
Point to the global - Add reserved words: static, interface, protected, etc.
Note: In ES6 modules, the top-level this is undefined and should not be used.
The export command
The first:
export var a = '123';
export const _b = '2323'
export let c = '2222'
Copy the code
The second:
var a = '123';
const _b = '2323'
let c = '2222'
export {a, _b, c}; / / recommend
Copy the code
Third (second based on the as keyword rename)
var a = '123';
const _b = '2323'
let c = '2222'
export {
a as stream1,
_b as stream2,
c as stream3 };
Copy the code
Note:
The interface output by the export statement is a reference to the corresponding value, that is, a dynamic binding relationship. Through this interface, the real-time value inside the module can be obtained.
Compare the CommonJS specification: The CommonJS module outputs a cache of values, with no dynamic updates.
The export command is required to be at the top of the module and will report an error if it is in the block-level scope, as will the import command.
The import command
The first:
import {a, _b ,c} from './profile'
Copy the code
The second:
import {stream1 as firstVal} from './profile'
Copy the code
Import is executed statically; expressions, variables, and if structures cannot be applied.
if(x == 1) {import { foo } from 'module1' }else{ / /... } Copy the code
Import statements are Singleton patterns: foo and bar are loaded in two statements, but they correspond to the same my_module instance.
import { foo } from './module1'
import { bar } from './module1'
/ / equivalent to
import {foo,bar} from './module1'
Copy the code
Overall loading of modules
You can use * to specify an object to which all output values are loaded:
import * as circle from './module1'
circle.foo();
circle.bar();
Copy the code
Runtime changes are not allowed because the whole module is loaded onto objects that can be analyzed statically.
import * as circle from './module1'
// The following two lines are disallowed
circle.foo = 123;
circle.bar = function(){}
Copy the code
The default output
The export default command can be the default output of the module
// module2.js
export default function(){
console.log('123')}/ / equivalent to
function a(){
console.log('123')}export {a as default};
Copy the code
The import command can specify any name for an anonymous function
import defaultFn from './module2'
/ / equivalent to
import {default as defaultFn} from './module2'
Copy the code
A compound of export and import
export { foo, bar} from 'my_module';
/ / is equivalent to
import {foo,bar} from 'my_module';
export{foo,bar};
Copy the code
export {es6 as default} from './someModule'
/ / is equivalent to
import {es6} from './someModule'
export default es6;
Copy the code
The import () method
As mentioned earlier, require is dynamically loaded, that is, it can be required at the time it is used; Imports, on the other hand, are statically executed and can only exist at the top level of code, not in block-level scope. This results in the import not being executed at run time (similar to AMD’s weakness). Hence the proposal to introduce an import() function, similar to Node’s require function (CommonJS), but with asynchronous loading.
Definition: The import() function takes the same arguments as import, returns a Promise object, and loads the value as a callback argument to the then method.
const main = document.querySelector('main')
import(`./section-modules/${someVariable}.js`)
.then(module= > {
module.loadPageInto(main);
})
.catch(err= > {
main.textContext = err.message;
})
Copy the code
// load interface parameters:
import('./module1.js')
.then(({default:defaultFn,foo,bar}) = > {
console.log(defaultFn)
})
Copy the code
// Load multiple modules at the same time and apply to async functions
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1,js'),
import('./module2.js'),
import('./module3.js')
])
}
main();
Copy the code
Loading between different specifications
Import loads the CommonJS module
-
When you load CommonJS modules with the import command, Node automatically treats the module.exports property as the module’s default output, which is equivalent to export default
// a.js module.exports = { foo: 'hello'.bar: 'world' } // When import is introduced export default { foo: 'hello'.bar: 'world' } Copy the code
-
The CommonJs module is a run-time determinate output interface, so when loading the CommonJs module with the import command, only the overall input (*) can be used.
import {readfile} from 'fs' // Error when 'fs' is CommonJS module // Overall input import * as express from 'express' const app = express.default(); Copy the code
Require Loads the ES6 module
-
When the require command loads an ES6 module, all output interfaces become attributes of the input object.
// es.js let foo = {bar : 'my-default'}; exxport default foo; foo = null; // cjs.js const es_namespace = require('./es') console.log(es_namespace.default);// {bar:'my-default'} Copy the code
4. Compare CommonJS
With a new love can not forget the old love, let’s continue to compare CommonJS and ES6 modular difference, further understand the characteristics of ES6 modular.
Copy of output values
The CommonJS module prints a copy of a value, ES6 prints a reference to a value
// lib.js
let num = 3;
function changeNum() {
num = 4;
}
module.exports = {
num: num,
changeNum: changeNum,
};
//main.js
var mod = require('./lib.js')
console.log(mod.num); / / 3
mod.changeNum();
console.log(mod.num); / / 3
Copy the code
This is because mod. Num is a primitive value and will be cached. We can get the internally modified value by writing it as a function:
// lib.js
let num = 3;
function changeNum() {
num = 4;
}
module.exports = {
get num(){
return num
},
changeNum: changeNum,
};
//main.js
var mod = require('./lib.js')
console.log(mod.num); / / 3
mod.changeNum();
console.log(mod.num); / / 3
Copy the code
Comparison of ES6 modules:
// lib.js
export let num = 3;
export function changeNum() {
num = 4;
}
//main.js
import {num,changeNum} from './lib.js'
console.log(num); / / 3
changeNum();
console.log(num); / / 4
Copy the code
CommonJS loop loading
Loading principle
CommonJS A module corresponds to a script file. The require command executes the entire script each time a module is loaded and then generates an object. This object, once generated, will be evaluated directly in the cache if the same require command is executed again. In other words, no matter how many times the CommonJS module is loaded, it only runs once on the first load and returns the result of the first run on subsequent loads, unless the system cache is manually cleared.
Cyclic loading
// a.js
exports.done = false;
var b = require('./b.js'); // 1. A. js suspends execution and switches to b.js; B :{done:true} b:{done:true}
console.log('in a.js, b.tone =%j',b.done); // 5. 'in a.js, b.one =true'
exports.done = true;
console.log('A.js completed') // 6. 'a.js completed '
// b.js
exports.done = false;
var a = require('./b.js') // 2. a:{done:false}
console.log('in b.js, a. tone =%j',a.done); // 3. 'in b.js, a.tone =false'
exports.done = true;
console.log('B.js completed') // 4. 'B. js completed', continue to execute a.js
// main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('in main.js, a.tone =%j, b.tone =%j',a.done,b.done); // 7.' in main.js, a.one =true, b.one =true'
Copy the code
In b.js, it does not execute b.js again when main.js reaches line 2, but prints the cached b.js result, that is, its fourth line: exports. Done = true
To sum up: 1. Because the CommonJS module encounters cyclic loading, it returns the value of the currently executed part of the code, not the value after all the code is executed (note step 2 above). 2.
Contrast: ES6 modules are dynamic references and variables are not cached
// a.js
import {bar} from './b.js';
export function foo(){
console.log('foo')
bar();
console.log('执行完毕')
}
foo();
// b.js
import {foo} from './a.js' // If it is CommonJS, undefined is returned and will not be changed
export function bar(){
console.log('bar')
if(Math.random() > 0.5){ foo(); }}// The result may be: foo bar completes
// The result can also be: foo bar foo bar Completion The execution is complete
Copy the code