An overview of the

Prior to ES6, there were several module loading schemes developed by the community, the most important being CommonJS and AMD. The former is used for servers and the latter for browsers. 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.

The ES6 module is designed to be as static as possible, so that the module dependencies, as well as the input and output variables, can be determined at compile time. Both CommonJS and AMD modules can only determine these things at runtime. For example, a CommonJS module is an object, and you have to look for object properties when you enter it.

/ / CommonJS module
let { stat, exists, readfile } = require('fs');

/ / is equivalent to
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
Copy the code

The above code essentially loads the FS module as a whole (that is, all the methods that load FS), generates an object (_fs), and then reads three methods from that object. This type of loading is called “runtime loading” because the object is only available at runtime, making it completely impossible to do “static optimization” at compile time.

An ES6 module is not an object. Instead, it explicitly specifies the output code through the export command, which is then input through the import command.

/ / ES6 module
import { stat, exists, readFile } from 'fs';
Copy the code

The essence of the above code is to load three methods from the FS module and none of the others. This type of loading is called “compile-time loading” or static loading, meaning ES6 modules can be loaded at compile time, which is much more efficient than CommonJS modules. Of course, this also makes it impossible to reference the ES6 module itself because it is not an object.

Strict mode

ES6 modules automatically adopt strict mode, whether or not you add “Use strict” to the module header; .

Strict mode has the following major limitations.

  • Variables must be declared before being used
  • Function arguments cannot have attributes of the same name; otherwise, an error is reported
  • You can’t usewithstatements
  • Cannot assign a value to a read-only attribute, otherwise an error is reported
  • Octal numbers cannot be represented with the prefix 0, otherwise an error is reported
  • You cannot delete attributes that cannot be deleted; otherwise, an error is reported
  • Cannot delete variablesdelete prop, an error is reported, and only attributes can be deleteddelete global[prop]
  • evalNo variables are introduced in its outer scope
  • evalandargumentsCannot be reassigned
  • argumentsDoes not automatically reflect changes in function parameters
  • You can’t usearguments.callee
  • You can’t usearguments.caller
  • banthisPointing to a global object
  • You can’t usefn.callerandfn.argumentsGets the stack of function calls
  • Added reserved words (e.gprotected,staticandinterface)

Modules must comply with all of the above restrictions. Because strict patterns were introduced in ES5 and are not part of ES6, please refer to the related ES5 books, which are not covered in detail.

In particular, notice the limitation of this. In ES6 modules, the top-level this refers to undefined, meaning that this should not be used in top-level code.

The export command

The function of the module consists of two commands: export and import. The export command is used to specify the external interface of a module, and the import command is used to input functions provided by other modules.

A module is an independent file. All variables inside the file are not available externally. If you want outsiders to be able to read a variable inside a module, you must use the export keyword to output that variable. Here is a JS file that uses the export command to output variables.

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
Copy the code

The above code is the profile.js file, which saves the user information. ES6 treats it as a module that prints three variables to the outside world using the export command.

So there’s another way to write export.

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };
Copy the code

The code above, following the export command, uses curly braces to specify the set of variables to output. It is equivalent to the former (placed directly before the VAR statement), but should be preferred. This way, at the end of the script, you can see what variables are being output.

The export command can output functions or classes as well as variables.

export function multiply(x, y) {
  return x * y;
};
Copy the code

The code above outputs a function called multiply.

Normally, the variable output by export is the original name, but can be renamed using the AS keyword.

function v1() {... }function v2() {... }export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
Copy the code

The above code renames the external interfaces of the functions v1 and v2 using the AS keyword. After the rename, V2 can be printed twice with different names.

It should be noted that the export command specifies the external interface and must establish a one-to-one correspondence with variables inside the module.

/ / an error
export 1;

/ / an error
var m = 1;
export m;
Copy the code

Both of the above methods give an error because there is no external interface provided. The first way to write it is to print 1 directly, and the second way to write it is to print m directly, again. 1 is just a value, not an interface. The correct way to write it is the following.

/ / write one
export var m = 1;

/ / write two
var m = 1;
export {m};

/ / writing three
var n = 1;
export {n as m};
Copy the code

The above three notations are correct, specifying the external interface M. Other scripts can use this interface to get the value 1. Their essence is to establish a one-to-one correspondence between the interface name and the module’s internal variables.

Similarly, the output of function and class must be written this way.

/ / an error
function f() {}
export f;

/ / right
export function f() {};

/ / right
function f() {}
export {f};
Copy the code

In addition, the interface output by the export statement has a dynamic binding relationship with its corresponding value, that is, the real-time value inside the module can be obtained through this interface.

export var foo = 'bar';
setTimeout(() = > foo = 'baz'.500);
Copy the code

The above code outputs a variable foo with a value of bar, which becomes baz after 500 milliseconds.

This is completely different from the CommonJS specification. The CommonJS module outputs a cache of values with no dynamic updates.

Finally, the export command can appear anywhere in the module, as long as it is at the top of the module. If you are in block-level scope, an error is reported, as is the import command in the next section. This is because you can’t do static optimizations when you’re in a conditional block, which goes against the design of the ES6 module.

function foo() {
  export default 'bar' // SyntaxError
}
foo()
Copy the code

In the above code, the export statement is placed inside the function, resulting in an error.

The import command

After the module’s external interface is defined using the export command, other JS files can load the module using the import command.

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}
Copy the code

The import command of the above code is used to load the profile.js file and enter variables from it. The import command accepts a pair of curly braces specifying the name of a variable to import from another module. The variable name inside the braces must be the same as the name of the external interface of the imported module (profile.js).

If you want to rename the input variable, the import command uses the as keyword to rename the input variable.

import { lastName as surname } from './profile.js';
Copy the code

The variables entered by the import command are read-only because it is by nature an input interface. That is, it is not allowed to rewrite interfaces in scripts that load modules.

import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;
Copy the code

In the above code, the script loads variable A and reassigns it to an error because A is a read-only interface. However, if A is an object, overwriting a’s properties is allowed.

import {a} from './xxx.js'

a.foo = 'hello'; // It is valid
Copy the code

In the code above, the property of A can be successfully overwritten, and other modules can also read the overwritten value. However, this method is very difficult to check, it is recommended that all input variables, as a completely read-only, do not easily change its properties.

The from after import specifies the location of the module file, which can be a relative or absolute path. If you don’t have a path but just a module name, you must have a configuration file that tells the JavaScript engine where the module is.

import { myMethod } from 'util';
Copy the code

In the above code, util is the module file name. Since there is no path, you must configure it to tell the engine how to fetch the module.

Note that the import command is promoted to the top of the module and executed first.

foo();

import { foo } from 'my_module';
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.

/ / an error
import { 'f' + 'oo' } from 'my_module';

/ / an error
let module = 'my_module';
import { foo } from module;

/ / an error
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}
Copy the code

All three of the above methods report errors because they use expressions, variables, and if constructs. None of these grammars can be evaluated in the static analysis phase.

Finally, the import statement executes the loaded module, so it can be written as follows.

import 'lodash';
Copy the code

The above code simply executes the Lodash module, but does not enter any values.

If the same import statement is repeated multiple times, it is executed once, not multiple times.

Overall loading of modules

In addition to specifying that an output value is loaded, you can also use global loading, where you specify an object with an asterisk (*) on which all output values are loaded.

Below is a circle. Js file that outputs two methods area and circumference.

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}
Copy the code

Now load the module.

// main.js

import { area, circumference } from './circle';

console.log('Circle area:' + area(4));
console.log('Circumference:' + circumference(14));
Copy the code

The above is written to specify the methods to be loaded one by one. The overall loading is written as follows.

import * as circle from './circle';

console.log('Circle area:' + circle.area(4));
console.log('Circumference:' + circle.circumference(14));
Copy the code

Note that the object to which the whole module is loaded (circle, for example) should be parsed statically, so runtime changes are not allowed. None of the following is allowed.

import * as circle from './circle';

// The following two lines are disallowed
circle.foo = 'hello';
circle.area = function () {};
Copy the code

Export the default command

Specify default output for the module

// export-default.js
export default function () {
  console.log('foo');
}
Copy the code

The above code is a module file, export-default.js, whose default output is a function.

The import command can specify any name for the anonymous function when the module is loaded by other modules.

// import-default.js
import customName from './export-default';
customName(); // 'foo'
Copy the code

The import command in the above code can point to the methods output by export-default.js with any name, so you do not need to know the function name output by the original module. Note that curly braces are not used after the import command.

The export default command can also be used in front of non-anonymous functions.

// export-default.js
export default function foo() {
  console.log('foo');
}

// or write it as

function foo() {
  console.log('foo');
}

export default foo;
Copy the code

In the above code, the function name foo of function foo is invalid outside the module. When loaded, it is treated as if an anonymous function is loaded.

Let’s compare the default output to the normal output.

/ / the first group
export default function crc32() { / / output
  // ...
}

import crc32 from 'crc32'; / / input

/ / the second group
export function crc32() { / / output
  // ...
};

import {crc32} from 'crc32'; / / input
Copy the code

The above code is written in two groups. In the first group, when export default is used, the corresponding import statement does not need to use curly braces. The second group is when export default is not used, the corresponding import statement needs to use curly braces.

The export default command is used to specify the default output of the module. Obviously, a module can have only one default output, so the export default command can only be used once. Therefore, the import command is not followed by parentheses, because it can only correspond to the export default command.

Essentially, export default simply prints a variable or method called default, and the system allows you to call it whatever you want. So, the following is valid.

// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
/ / is equivalent to
// export default add;

// app.js
import { default as foo } from 'modules';
/ / is equivalent to
// import foo from 'modules';
Copy the code

Because the export default command simply prints a variable named default, it cannot be followed by a variable declaration statement.

/ / right
export var a = 1;

/ / right
var a = 1;
export default a;

/ / error
export default var a = 1;
Copy the code

In the above code, export default A means to assign the value of variable A to variable default. So, the last one will give you an error.

Similarly, because the essence of the export default command is to assign the following value to the default variable, you can write a value directly after export Default.

/ / right
export default 42;

/ / an error
export 42;
Copy the code

In the above code, the latter statement fails because the external interface is not specified, while the former statement specifies the external interface is default.

With the export default command, it is straightforward to enter modules, such as the Lodash module.

import _ from 'lodash';
Copy the code

If you wanted to enter both default methods and other interfaces in an import statement, you could write it as follows.

import _, { each, forEach } from 'lodash';
Copy the code

The export statement corresponding to the above code is as follows.

export default function (obj) {
  / /...
}

export function each(obj, iterator, context) {
  / /...
}

export { each as forEach };
Copy the code

The last line of the code above exposes the forEach interface, which by default points to each, meaning that forEach and each point to the same method.

Export default can also be used to output classes.

// MyClass.js
export default class {... }// main.js
import MyClass from 'MyClass';
let o = new MyClass();
Copy the code

A compound of export and import

If the same module is input and then output within a module, the import statement can be written together with the export statement.

export { foo, bar } from 'my_module';

// It can be used as an example
import { foo, bar } from 'my_module';
export { foo, bar };
Copy the code

In the above code, the export and import statements can be combined on one line. Note, however, that after a single line, foo and bar are not actually imported into the current module, but are simply forwarded to the interface, preventing foo and bar from being used directly by the current module.

The interface name and overall output of a module can also be written in this way.

// The interface was renamed
export { foo as myFoo } from 'my_module';

// Total output
export * from 'my_module';
Copy the code

The default interface is written as follows.

export { default } from 'foo';
Copy the code

The named interface is changed to the default interface as follows.

export { es6 as default } from './someModule';

/ / is equivalent to
import { es6 } from './someModule';
export default es6;
Copy the code

Similarly, the default interface can be renamed to a named interface.

export { default as es6 } from './someModule';
Copy the code

Before ES2020, there was an import statement with no corresponding compound.

import * as someIdentifier from "someModule";
Copy the code

ES2020 adds this notation.

export * as ns from "mod";

/ / is equivalent to
import * as ns from "mod";
export {ns};
Copy the code

Module inheritance

Modules can also inherit from one another.

Suppose you have a circlePlus module that inherits the Circle module.

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}
Copy the code

Export * in the code above represents reprinting all the attributes and methods of the Circle module. Note that the export * command ignores the circle module’s default method. The code then prints out the custom e variable and default methods.

In this case, you can rename the circle property or method and then print it.

// circleplus.js

export { area as circleArea } from 'circle';
Copy the code

Print only the area method of the circle module and rename it circleArea.

Loading the above module is written as follows.

// main.js

import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));
Copy the code

Import exp in the above code indicates that the circlePlus module’s default method is loaded as exp.

Cross module constant

When we introduced the const command in this book, we said that a const declared constant is only valid for the current code block. If you want to set a constant across modules (that is, across multiple files), or if a value is shared by multiple modules, you can write it as follows.

/ / the js module
export const A = 1;
export const B = 3;
export const C = 4;

/ / test1. Js module
import * as constants from './constants';
console.log(constants.A); / / 1
console.log(constants.B); / / 3

/ / test2. Js module
import {A, B} from './constants';
console.log(A); / / 1
console.log(B); / / 3
Copy the code

If you need a lot of constants, you can create a special directory for constants and keep the constants in separate files.

// constants/db.js
export const db = {
  url: 'http://my.couchdbserver.local:5984'.admin_username: 'admin'.admin_password: 'admin password'
};

// constants/user.js
export const users = ['root'.'admin'.'staff'.'ceo'.'chief'.'moderator'];
Copy the code

Then, merge the output constants from these files into index.js.

// constants/index.js
export {db} from './db';
export {users} from './users';
Copy the code

To use it, simply load index.js.

// script.js
import {db, users} from './constants/index';
Copy the code

The original link

ES6 module