background
We all know that the evolution of JS modularity has come a long way, from the original CommonJS, to later AMD and CMD, to today’s ES6 modularity scheme. Survival of the fittest, for the JS language, mainly used in the Node terminal modular scheme CommonJS survived, and ES6 launched modular scheme has won everyone’s recognition, it is likely to become the main modular scheme of JS in the future. You may have wondered: what are the advantages of both modularity solutions, since they can be used and recognized by everyone? What are the differences between the two modular solutions? With my doubts, I also read some articles and found that the summary was not very comprehensive, so I want to write an article to fully summarize their differences.
Before starting the body, I have another question: why use two modularity schemes when they are both JS?
Why not use CommonJS in the browser
Before we answer this question, we need to be aware of the fact that the CommonJS require syntax is synchronous. When loading a module using require, we must wait for the module to complete before executing the following code. If we knew this fact, our question would be easily answered. NodeJS is a server that uses the require syntax to load modules, usually a file, and only needs to read the file from the local hard disk, which is relatively fast. However, on the browser side, the file is generally stored on the server or CDN. If a module is loaded synchronously, the speed will be determined by the network, and the time may be very long. In this way, the browser is easy to enter the “suspended animation state”. Hence the AMD and CMD modular solutions, which load asynchronously and are suitable for use on the browser side.
Well, with the first question out of the way, let’s get down to business.
Two big differences
I’m sure you’ve heard of some of the differences between them, since we’re all dealing with them in our daily development. In fact, the two biggest differences are:
- The CommonJS module prints a copy of the value, while the ES6 module prints a reference to the value.
- The CommonJS module is run time loaded, and the ES6 module is compile time output interface.
Let’s look at the first difference.
CommonJS prints a copy of the value. In other words, once a value is printed, subsequent changes within the module do not affect the external use of the value. Specific examples:
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
Copy the code
We then use this module in other files:
var mod = require('./lib');
console.log(mod.counter); / / 3
mod.incCounter();
console.log(mod.counter); / / 3
Copy the code
The above example fully illustrates that if we output the counter variable, its value will not change even if we call the incCounter method inside the module to modify its value.
ES6 modules operate in a completely different way. When the JS engine statically analyzes scripts, it generates a read-only reference when it encounters the module loading command import. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference.
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); / / 3
incCounter();
console.log(counter); / / 4
Copy the code
As shown in the above code, the variable counter of the ES6 module import is mutable, completely reflecting the changes in the module lib.js in which it is located.
The second difference is one of the biggest reasons why ES6 modules are so popular. We know that CommonJS actually loads an object, which is only generated once when the script runs, as we’ll explain later. However, ES6 module is not an object, its external interface is only a static definition, which will be generated in the code static parsing stage, so that we can use a variety of tools to rely on JS module analysis, optimize the code, The Tree Shaking and Scope collieries in Webpack actually rely on ES6 modularization.
Circular Dependency
Looping means that script A depends on script B, and script B’s execution depends on script A. In a large project, dependencies are often complex, and cyclic dependencies can easily occur, so for a modular solution, you need to consider this situation.
CommonJS loop loading
To understand the CommonJS looping problem, we need to take a look at how it works. A CommonJS module, typically a file, generates an object in memory the first time a module is loaded using Reqiure. It looks something like this:
{
id: '... '.exports: {... },loaded: true. }Copy the code
Exports is the interface that the module exports. The loaded attribute indicates whether the module has completed execution. The module will be exported directly from the exports property of the object in the future. Even if a module’s require command is executed multiple times, it will only be run once on the first load and then read from the cache unless the cache is manually cleared.
The CommonJS module is designed to be executed on load, and when the script is reqiure, it will be executed in full. Once a module is “looping”, only the parts that have been executed are printed, not the parts that have not been executed. Let’s look at an official example, first define a.js as follows:
exports.done = false;
var b = require('./b.js');
console.log('in a.js, b.tone = %j', b.done);
exports.done = true;
console.log('A.js completed');
Copy the code
The above code first prints a done variable and then starts loading B.js. Note that a.js will stop here and wait for B.js to finish before continuing with the code. Redefine b.js code:
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('B.js completed');
Copy the code
Similar to a.js, b.js exports a variable and starts loading A.js in the second line, causing a cyclic dependency. The done variable is returned from exports of the memory object. The done variable is returned from exports of the memory object. Then B. JS continues to execute the following code, and after the execution is completed, the execution power is returned to A. JS and the rest of the code is executed. To verify this process, create a new main.js:
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
Copy the code
The result of executing main.js is:
In B. js, A. Tone =falseB. Js Execution completed in A. Js, b. Don=trueIn main.js, a.stone =true, b.done=true
Copy the code
Since the CommonJS module encounters a looping load and outputs the value of the part of the code that has already been executed, rather than the value after the code has all been executed, the two may differ. So you have to be very careful when you enter variables.
Cyclic loading in ES6
The ES6 module is a dynamic reference. If you load a variable with import, the variable will not be cached and the final value will be fetched when it is actually evaluated. Consider the following example:
// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n === 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
returnn ! = =0 && even(n - 1);
}
Copy the code
In the code above, the function even in even.js takes an argument n, which is subtracted by 1 as long as it is not equal to 0, passing the loaded odd(). Odd.js does something similar.
Running the above code results in the following:
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
Copy the code
In the code above, even() is executed six times as n goes from 10 to 0, so the variable counter is equal to 6. In this example, we can see that the value of the ‘counter variable output in even.js changes as it changes within the module.
Because of the different loading methods of the two modular schemes, they treat cyclic loading differently.
What else is different about them
There are, of course, some other differences, which we list directly here. First, there is this keyword. At the top of ES6 module, this refers to undefined. The this at the top of the CommonJS module points to the current module. Second, you can load CommonJS modules directly in ES6 modules, but only as a whole, not as a single output item.
/ / right
import packageMain from 'commonjs-package';
/ / an error
import { method } from 'commonjs-package';
Copy the code
Node.js is a bit more cumbersome with ES6 modules because it has its own CommonJS module specification that is incompatible with the ES6 module format. Currently, the two modules are handled separately. Starting with v13.2, Node.js has ES6 module support enabled by default. NodeJS requires ES6 modules to use the MJS suffix file name. Whenever NodeJS encounters a file ending in MJS, it considers ES6 modules. In addition to changing the file’s suffix, you can of course specify the type field as module in your project’s package.json file.
{
"type": "module"
}
Copy the code
However, the require command cannot load the.mjs file, and an error is reported. Only the import command can load the.mjs file. On the other hand, the.mjs file cannot use require command, must use import, so in normal development, ES6 module and CommonJS module should not be mixed.
conclusion
This is the end of this article. Let’s summarize the content covered in the article:
- becauseCommonJSthe
require
The syntax is synchronous, so it causesCommonJSThe module specification is only suitable for use on the server side, while the ES6 module can be used on both the browser side and the server side. However, there are some special rules to use on the server side. - The CommonJS module prints a copy of a value, while the ES6 module prints a reference to a value.
- CommonJS module is loaded at runtime, while ES6 module is output interface at compile time, which makes it possible to do static analysis of JS modules.
- Because of their different loading mechanisms, the two modules behave differently when it comes to cyclic loading.CommonJSIn case of loop dependency, only the executed part is output, and subsequent output or changes do not affect the output variable. The ES6 module, by contrast, uses
import
When you load a variable, it’s not cached, it gets the final value when it’s actually evaluated; - About the top level of the module
this
Point to the problem inCommonJSThe top floor,this
Point to the current module; In the ES6 module,this
Point to theundefined
; - On the issue of two modules referencing each other, in ES6 modules, loading is supportedCommonJSThe module. But on the other hand,CommonJSDoes not
require
ES6 module, in NodeJS, the two module schemes are handled separately.
Reference
The loading implementation of Module
Thoroughly understand CommonJS and AMD/CMD for modular programming
ECMAScript Modules