The basic concept
Modules are a mechanism for breaking up JavaScript programs into separate modules that can be imported on demand. 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. Previously, the browser side mainly relies on building tools to simulate the implementation of the Module system, and now the latest browser has begun to gradually support ES Module, esModule (here is the spelling in webpack, ES Module, Obscure the term ES6 Module) is becoming a common Module solution for browsers and servers
The esModule has the following features
esModule
Is not the objectesModule
Loading is compile-time loading (static loading), that is, the module is loaded when the compilation is completeesModule
The strict mode is automatically adopted. The restrictions in strict mode are as follows:- Variables must be declared before being used
- The top of the
this
Point to theundefined
Does not point to a global object - Function parameters cannot have attributes of the same name
- You can’t use
with
- Read-only attributes cannot be assigned
- Can’t use prefixes
0
Octal - Cannot delete undeletable attributes
eval
No variables are introduced in its outer scopeeval
和arguments
Cannot be reassignedarguments
Does not automatically reflect changes in function parameters- You can’t use
arguments.callee
- You can’t use
arguments.caller
- You can’t use
fn.caller
和fun.arguments
Function call stack - Added reserved words
EsModule consists mainly of two commands: export (to print a specified code block) and import (to enter the functionality of other modules).
This article follows the MDN saying, import and export content is called interface, this definition is different from other programming languages
export
A module is a separate file that contains variables that cannot be accessed 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.
Export Exports bound functions, objects, or raw values from a module that can be used by other modules through import statements.
There are two export methods:
- Named exports
- You can export variables, functions, or classes
- You can rename the exported values using the AS keyword
// export const firstName = 'rede'; export const lastName = 'li'; . Const firstName = 'rede'; const lastName = 'li'; export {firstName, lastName}; . Export function multiply (x, y) {return x * y; }... Function v1() {} function v2() {} export {streamV1 as streamV1, v2 as streamV2}Copy the code
- Export default
- Each module can contain only one default export
export default
Essentially, I’m going to assign the value directly todefault
Variable, so it can’t be followed by a variable declaration statement.
/// const name = 'index'; const info = 'index-info'; const age = 'index-age'; export {info, age}; export default name; . /// main.js import index from './index.js'; import {info, age} from './index.js'; console.log(index); // index console.log(info, age); / / index - info index - the age / / / index, js is derived by default in the name, so in the main. The index is derived by default name in js, because the info and the age is named export, so to introduce should also be clear... Export default 42; . Export default var a = 1; export default var a = 1; . Export default function foo() {console.log('foo'); /// export default function foo() {console.log('foo'); }Copy the code
export
Dynamic binding of valuesexport
The output interface of a statement is dynamically bound to its value. When the internal variable changes, the imported value changes with it
// index.js export let foo = 'bar'; setTimeout(() => foo = 'baz', 500); // Change variable value dynamically after 0.5s... // main.js import {foo} from './index'; console.log(foo); setTimeout(() => { console.log(foo); }, 600); // bar ** 0.5s after // bazCopy the code
export
Abnormal condition of
Export specifies that the external interface must establish a one-to-one relationship with the internal variables of the module. Direct output of external data will result in an error
export 1; // SyntaxError: Unexpected token, expected ... Const m = 1; // const m = 1; export m; . Function f() {}; function f() {}; export f; / / an errorCopy the code
Export must be at the top level of the module and cannot be in any block-level scope
function foo () { export default 'bar'; /// cannot be in block-level scope} foo(); / / an errorCopy the code
import
Once export is used to define the external interface, other modules can load this module with import to import related content. Import commands are statically parsed by the JavaScript engine and executed before other statements in the module
- Namespace Imports import the contents of the entire module
/// list.js export const name = 'list.js'; export const age = 18; . /// index.js import * as list from './list'; console.log(list.name, list.age);Copy the code
- Named Imports import a specific interface from a module
- You can customize the import name
/// list.js export const name = 'list.js'; export const age = 18; . /// index.js import { name, age} list from './list.js'; console.log(name, age); . Import {name as na} from './list.js';Copy the code
-
Default Import
- This corresponds to the default export if the corresponding export file is not used
export default
, the imported value will be undefined
- This corresponds to the default export if the corresponding export file is not used
-
Empty Import
- Only the module code is loaded, but no new objects are created
import './module.js';
Copy the code
import
Abnormal situation
Import loaded variables are read-only and cannot be modified
import {name} from './index.js'; name = 'main.js'; SyntaxError: "name" is read-onlyCopy the code
Import must clearly indicate the file to load, and cannot use expressions or variables because these have results only at run time
Import {'f' + 'oo'} from 'my_module'; // let module = 'my_module'; import { foo } from module; If (x === 1) {import {foo} from 'module1'; } else { import { foo } from 'module2'; } /// all three types of syntax will return an error, because in the static analysis phase, the syntax has not been executed, so it is impossible to know which modules need to be loaded... Import './index.js'; import './index.js'; import './index.js'; import './index.js'; . // Bad import {foo} from './index.js'; import {baz} from './index.js'; // good import {foo, baz} from './index.js';Copy the code
Related Suggestions
Instead of using the whole module load (*), load whatever functionality is needed from the specified module
/// list.js export const name = 'list.js'; export const age = 18; . /// index.js import * as list from './list'; console.log(list.name); Import {name} from './list' import {name} from './list';Copy the code
Do not use the combination of export and import
The process of export and import takes place in the compilation stage. There is no difference between writing them together and writing them separately. There is no performance improvement or other advantages
// bad export { firstName as default } from './index'; Import {firstName} from './index'; export default firstName;Copy the code
other
- Cross module constant
- This is mainly a project management idea, we can extract the page public variables into a module, the need to use these public variables to introduce as needed, an obvious example is the management of interface URL
// urls.js export const link1 = '... '; export const link2 = '... '; . /// we need to use the requested module. This way we can store all urls in the same file and import import {link1} from 'urls.js' as needed;Copy the code
import()
Import () is a proposal because imports cannot be dynamically loaded and addresses the following issues
- According to the need to load
- Conditions of loading
- Dynamic module path
The first two are now available with WebPack
If (true) {// conditional load import(/* webpackChunkName:"main" */'./main'). Then (item => {console.log(item.main); }); } else {import(/* webpackChunkName:"list" */'./list').then(item => {console.log(item.list); }); }... // Load multiple modules simultaneously, Promise.all([import(/* webpackChunkName:"main"*/'./main'), import(/* webpackChunkName:"main1"*/'./main1')), import(/* webpackChunkName:"main2"*/'./main2'), ]).then(([main, main1, main2]) => { console.log(main); console.log(main1); console.log(main2); })Copy the code
The Module is loaded
Browser loading JS
In a web page, the browser loads JavaScript through the
If the script is large, it will take a long time to download and execute, clogging the browser, and the user will feel that the browser is “stuck” without any response.
You can add the defer or Async properties on the
defer
It will not execute until the entire page has been properly rendered in memory (the DOM structure has been fully generated and other scripts have been executed)defer
Render it before you execute it- multiple
defer
Steps are also loaded in the order they appear on the page
async
Once the download is complete, the rendering engine interrupts the rendering, executes the script, and then continues rendering.async
Execute as soon as you download itasync
Is more arbitrary, do not guarantee the loading order
The browser loads the esModule
If your browser doesn’t already support it, you can turn on the browser’s experimental Web platform features and leave chrome to find a solution
chrome://flags/#enable-experimental-web-platform-features
// set script type to module <script type="module"> import foo from './foo.js'; console.log(foo); </script>Copy the code
esModule
与CommonJS module
The difference between-
The CommonJS module prints a copy of the value, and the esModule prints a reference to the value
CommonJS module
Since it is a value copy, once the value is printed, the changes in the module do not affect the external reference, except if the output is a functionCommonJS
There’s also a way to get the value of the internal changeesModule
Operation mechanism andCommonJS
Different,JS engine
Static analysis of the script, if encounteredimport
Command to generate a read-only reference to the loaded module, which is only evaluated when the script is actually executed. Therefore, when the internal value of the module changes, the external value will naturally change
-
CommonJS modules are loaded at run time and ESModules are output at compile time
-
The CommonJS module require() is synchronously loaded, and the esModule import command is asynchronously loaded, with a separate module-dependent parsing phase
-
Starting with Node.js v13.2, esModule support is enabled by default. Node.js requires esModules to have.mjs file names. Node.js considers the.mjs file as an ES6 module when it encounters it. If you don’t want to change the suffix to.mjs, you can specify the type field as module in your project’s package.json file
{
"type": "module"
}
Copy the code
-
Path resolution rule of esModule in Node
- If the import module does not contain a path, it will
node_modules
Look for the module below package.json
The file has two fields to specify the entry file for the module:main
,exports
main
Entry file for module loading- Such as
import { something } from 'es-module-package';
To the Node. Js will be./node_modules
Under the directory, findes-module-package
Module, and then based on that modulepackage.json
的main
Field to execute the entry file
- Such as
exports
Field can specify the alias of a script or subdirectory, and the field has a higher priority thanmain
field"exports": {"./submodule": "./src/submodule.js"}
The specifiedsrc/submodule.js
An alias forsubmodule
If I introduce thetaimport submodule from 'es-module-package/submodule';
Is actually loaded./node_modules/es-module-package/src/submodule.js
- If the imported script file does not have a suffix such as
import './foo'
- Node tries to press in turn
.mjs
,.js
,.json
,.node
To try to load - If none of the above scripts exist Node will be loaded
package.json
In themain
Field specifies the script - If the corresponding file or field does not already exist, it will attempt to load it in turn
./foo/index.mjs
,./foo/index.js
,./foo/index.json
,./foo/index.node
- If it still does not exist, an error is reported
- Node tries to press in turn
- If the import module does not contain a path, it will
-
EsModule is also available in the browser environment. The browser can use esModule for
- Esmodules are loaded asynchronously, which is equivalent to being opened
defer
Properties. - If the page has more than one
<script type="module">
They are executed in the order they appear on the page- If it’s actively set
async
Property, in which case the rendering engine will interrupt the rendering execution as soon as the load is complete, not in the order it appears on the page
- If it’s actively set
- Esmodules are loaded asynchronously, which is equivalent to being opened
The “naked” import syntax is not supported in type=”module” mode (Node path lookup is not supported)
// The directory structure is as follows: Since the local service is http://127.0.0.1:8080 | - index. HTML | - SRC | - script | - index. Js / / / index, js export const info = 'index. Js'; . / / / index. The HTML < script type = "module" > / / / support absolute URL import {info} from 'http://127.0.0.1:8080/src/script/index.js'. console.log(info); </script> /// / support relative path, must be "/", "./", or ".. Import {foo} from '/ SRC /script/index.js'; Import {foo} from './ SRC /script/index.js'; Import {foo} from '.. /bar.js'; / / to.. Import {foo} from 'SRC /script/index.js' is not supported;Copy the code
Backward compatibility using the nomodule attribute (same effect as earlier NoScript)
<script type="module" src="./foo.js"></script>
<script nomodule src="./foo.nomodule.js"></script>
Copy the code
In type=”module” mode, the script tag for the external chain defaults to defer
<script type="module" src="1.js"></script> <script src="2.js"></script> <script defer src="3.js"></script> /// The three scripts are executed in the order of 2.js, 1.js, and 3.js. In addition, 1.js and 3.js do not block DOM renderingCopy the code
In type=”module” mode, the inline script will also defer
<! Inline module --> <script type="module"> addTextToBody("Inline module executed"); </script> <script src="1.js"></script> <! -- normal Inline script --> <script defer> addTextToBody("Inline script executed"); </script> <script defer SRC ="2.js"></script> // The order of execution is 1.js, normal inline script, inline module, 2.jsCopy the code
Normal inline scripts ignore the defer attribute, while inline Module scripts are always deferred, regardless of whether it has import behavior, and since the inline Module precedes the externalized 2.js definition, the execution order precedes 2.js
Async works equally well with external and inline modules Scripts
<! <script async type="module"> import {addTextToBody} from './utils.js'; addTextToBody('Inline module executed.'); </script> <! <script async type="module" SRC ="1.js"></script>Copy the code
The async property allows scripts to be loaded without hindering the HTML parser and executed immediately after loading. Unlike normal inline scripts, async properties also work on inline modules scripts and may not be executed in the same order as they appear in the DOM
In type=”module” mode, the command is executed only once, even if it is imported several times
<! -- 1.js executes only once --> <script type="module" SRC ="1.js"></script> <script type="module" SRC ="1.js"></script> <script type="module"> import "./1.js"; </script> <! - and regular script will perform multiple -- > < script SRC = "2. Js" > < / script > < script SRC = "2. Js" > < / script >Copy the code
Unlike normal script tags, type=”module” mode has restrictions on loading js that are not in the same domain
/ / / assumption on the http://127.0.0.1:8080/index.html page, Under the load of js < script type = "module" SRC = "http://localhost:3111/index.js" > < / script > / / / will prompt cross-domain, Access-control-allow-origin is not set. If type="module" is not added, js will load normallyCopy the code
When the request is in the same domain, most CER-based APIs send credentials (cookies, etc.), with the exception of FETCH () and Module Scripts, which do not send credentials unless manually declared.
You can add the Crossorigin attribute so that you can carry the credentials with you when you request them (Crossorigin =”use-credentials”). Note that the domain receiving the Credentials must return a response header of Access-Control-allow-credentials: true, indicating that clients are allowed to carry authentication information, such as cookies. In this way, the client can carry relevant authentication information when making a cross-domain request
<! - credentials (cookies, etc.) -- -- > < script SRC = "1. Js" > < / script > <! - there is no confidential information - > < script type = "module" SRC = "1. Js" > < / script > <! <script type="module" crossorigin SRC ="1.js?" ></script> <! <script type="module" crossorigin SRC ="https://other-origin/1.js"></script> <! <script type="module" crossorigin="use-credentials" SRC ="https://other-origin/1.js?" ></script>Copy the code