Module Syntax

<>Module Syntax

<> Summary

Historically,JavaScript There has been no module(module) system, Cannot split a large program into interdependent small files, Put them together in a simple way. It's available in other languages, such as
Ruby Ofrequire,Python Ofimport, Even even CSS Both [email protected], however JavaScript
There's no support for that, It's important to develop, Complex projects create huge obstacles.

stay ES6 before, The community has developed some module loading schemes, The main ones are CommonJS and AMD two types. Former for server, The latter is used for browsers.ES6
On the level of language standard, Module function is realized, And it's quite simple, Completely replaceable CommonJS and AMD Standard, Become a common module solution for browser and server.

ES6 Design idea of module, It's as static as possible, Enables module dependencies to be determined at compile time, And input and output variables.CommonJS and AMD
Modular, You can only identify these things at run time. such as,CommonJS Modules are objects, Object properties must be found on import.
// CommonJS Modular let { stat, exists, readFile } = require('fs'); // Equate to let _fs =
require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile =
_fs.readfile;
The essence of the above code is overall loadingfs Modular( That is loadingfs All methods of), Generate an object(_fs), Then read from the object 3
One way. This load is called“ Load at run time”, Because only the runtime can get this object, So there's no way to do it at compile time“ Static optimization”.

ES6 Module is not an object, But throughexport Command explicitly specifies the output code, Re passimport Command input.
// ES6 Modular import { stat, exists, readFile } from 'fs';
The essence of the above code is fromfs Module loading 3 One way, Other methods do not load. This load is called“ Load at compile time” Or static load, Namely ES6 Module loading can be done at compile time, Efficiency ratio
CommonJS High loading mode of module. Of course, It also makes it impossible to quote ES6 Module itself, Because it's not an object.

Because ES6 Modules are loaded at compile time, Make static analysis possible. With it, Can be further broadened JavaScript Syntax, For example, introduce macro(macro) And type inspection(type
system) These functions can only be realized by static analysis.

In addition to the benefits of static loading,ES6 The module also has the following benefits.

* No longer neededUMD Module format, In the future, both server and browser will support ES6 Module format. at present, Through various tool Libraries, In fact, this has been done.
* New browser in the future API Can be provided in module format, It no longer has to be a global variable ornavigator Object properties.
* Object is no longer required as a namespace( such asMath object), In the future, these functions can be provided through modules.
This chapter is an introduction. ES6 Module syntax, The next chapter describes how to use the Node In, Load ES6 Modular.

<> Strict mode

ES6 Strict mode is adopted automatically in the module of, Whether or not you add"use strict";.

The strict model mainly has the following limitations.

* Variables must be declared before they can be used
* A parameter of a function cannot have a property with the same name, Otherwise, report wrong.
* Out of commissionwith Sentence
* Cannot assign to read-only property, Otherwise, report wrong.
* Cannot use prefix 0 Represents octal number, Otherwise, report wrong.
* Properties that cannot be deleted cannot be deleted, Otherwise, report wrong.
* Cannot delete variabledelete prop, Report wrong, Only attributes can be deleteddelete global[prop]
* eval Does not introduce variables in its outer scope
* eval andarguments Cannot be reassigned
* arguments Does not automatically reflect changes in function parameters
* Out of commissionarguments.callee
* Out of commissionarguments.caller
* prohibitthis Point to global object
* Out of commissionfn.caller andfn.arguments Get the stack of function calls
* Reserved words added( such asprotected,static andinterface)
These restrictions, Modules must comply. Because the strict pattern is ES5 Introduced, Not belong to ES6, So please refer to the relevant ES5 book, This book is no longer detailed.

among, Special attentionthis Restrictions.ES6 Module, top-levelthis pointundefined, That is, it should not be used in the top-level codethis.

<>export command

Module function is mainly composed of two commands:export andimport.export Command is used to specify the external interface of the module,import Commands are used to enter functions provided by other modules.

A module is a separate file. All variables within the file, External cannot get. If you want the external to be able to read a variable inside the module, Must useexport Keyword output the variable. Here is a
JS file, Use insideexport Command output variable.
// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'
;export var year = 1958;
The code above isprofile.js file, Saved user information.ES6 Think of it as a module, Inside useexport The command outputs three variables externally.

export Writing, Except like above, There's another one.
// profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year =
1958; export {firstName, lastName, year};
The above code is inexport Command behind, Use braces to specify a set of variables to output. It and the former( Directly placed invar
Statement before) It is equivalent. But we should give priority to this writing method. Because it's at the end of the script, See what variables are output at a glance.

export Commands except output variables, You can also output functions or classes(class).
export function multiply(x, y) { return x * y; };
The above code outputs a functionmultiply.

under normal conditions,export The output variable is the original name, But you can useas Keyword rename.
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as
streamV2, v2as streamLatestVersion };
Use of the above codeas Keyword, Renamed functionv1 andv2 External interface of. After renaming,v2 It can be output twice with different names.

What needs special attention is,export The order specifies an external interface, One to one correspondence must be established with the variables inside the module.
// Report errors export 1; // Report errors var m = 1; export m;
Both of the above methods will report errors, Because no external interface is provided. The first writing method is direct output 1, The second way is through variablesm, Or direct output 1.1 It's just a value, Not interface. The correct way to write it is as follows.
// Write a way export var m = 1; // Writing two var m = 1; export {m}; // Writing three var n = 1; export
{nas m};
All three of the above are correct, Specified external interfacem. Other scripts can use this interface, Fetch value1. Their essence is, Between interface name and module internal variables, A one-to-one correspondence is established.

Alike,function andclass Output, It must also be written in this way.
// Report errors function f() {} export f; // Correct export function f() {}; // Correct function f(
){} export {f};
in addition,export Interface for statement output, The corresponding value is a dynamic binding relationship, Through this interface, It can get the real-time value inside the module.
export var foo = 'bar'; setTimeout(() => foo = 'baz', 500);
Output variable of the above codefoo, The value isbar,500 Milliseconds laterbaz.

This point and CommonJS Specifications are totally different.CommonJS The module outputs a cache of values, There is no dynamic update, See below.《Module Load implementation of》 Section.

Last,export Commands can appear anywhere in the module, As long as it is at the top of the module. If in block level scope, You will report wrong. Next sectionimport
So is the order. This is because it is in a conditional block of code, You can't do static optimization, Contrary to ES6 Original intention of module design.
function foo() { export default 'bar' // SyntaxError } foo()
In the above code,export Statement in function, Result reported wrong.

<>import command

Useexport After the command defines the external interface of the module, Other JS The file can be passedimport Command to load this module.
// main.js import {firstName, lastName, year} from './profile'; function
setName(element) { element.textContent = firstName + ' ' + lastName; }
Code aboveimport command, Used for loadingprofile.js file, And enter variables from it.import
Command accepts a pair of braces, It specifies the variable name to be imported from other modules. Variable names in braces, Must be associated with the imported module(profile.js) The name of the external interface is the same.

If you want to rename the input variable,import Command to useas Keyword, Rename the input variable.
import { lastName as surname } from './profile';
import Hinderfrom Specify the location of the module file, Can be a relative path, It can also be an absolute path,.js Suffixes can be omitted. If it's just the module name, Without path, Then there must be a profile, tell
JavaScript Engine location of the module.
import {myMethod} from 'util';
In the above code,util Is the module filename, Because there is no path, Must be configured by, Tell the engine how to get this module.

Be careful,import Command has promotion effect, It will be lifted to the head of the whole module, First execution.
foo(); import { foo } from 'my_module';
The above code will not report an error, becauseimport Execution of is earlier thanfoo Call. The essence of this behavior is,import Commands are executed during the compile phase, Before the code runs.

Becauseimport Is static execution, So you can't use expressions and variables, These syntax structures are only available at run time.
// Report errors import { 'f' + 'oo' } from 'my_module'; // Report errors let module = 'my_module';
import { foo } from module; // Report errors if (x === 1) { import { foo } from 'module1';
}else { import { foo } from 'module2'; }
All of the above three writing methods will report errors, Because they use expressions, Variable sumif structure. In the static analysis stage, These grammars don't get value.

Last,import Statement executes the loaded module, Therefore, it can be written as follows.
import 'lodash';
The above code only executeslodash Modular, But no value is entered.

If you repeat the same sentence multiple timesimport Sentence, Then only once, Not more than once.
import 'lodash'; import 'lodash';
The above code has been loaded twicelodash, But only once.
import { foo } from 'my_module'; import { bar } from 'my_module'; // Equate to import
{ foo, bar }from 'my_module';
In the above code, althoughfoo andbar Load in two statements, But they are the samemy_module Example. In other words,import Statement is Singleton Pattern.

Current stage, adopt Babel transcoding,CommonJS Modularrequire Command and ES6 Modularimport
command, Can be written in the same module, But it's better not to. becauseimport Execute in static parsing phase, So it's the first module to execute. The following code may not get the expected result.
require('core-js/modules/es6.symbol'); require('core-js/modules/es6.promise');
import React from 'React';
<> Overall loading of modules

In addition to specifying to load an output value, You can also use bulk loading, Asterisk(*) Specify an object, All output values are loaded on this object.

Here is acircle.js file, It outputs two methodsarea andcircumference.
// circle.js export function area(radius) { return Math.PI * radius * radius; }
export function circumference(radius) { return 2 * Math.PI * radius; }
Now, Load this module.
// main.js import { area, circumference } from './circle'; console.log(' Round area:'
+ area(4)); console.log(' Circumference of circle:' + circumference(14));
The above method is to specify the method to be loaded one by one, The whole load is written as follows.
import * as circle from './circle'; console.log(' Round area:' + circle.area(4));
console.log(' Circumference of circle:' + circle.circumference(14));
Be careful, The object where the module is loaded as a whole( The above example iscircle), It should be able to analyze statically, So runtime changes are not allowed. The following are not allowed.
import * as circle from './circle'; // The following two lines are not allowed circle.foo = 'hello';
circle.area =function () {};
<>export default command

As can be seen from the previous example, Useimport
When ordered, The user needs to know the variable name or function name to load, Otherwise, it cannot be loaded. however, Users definitely want to get started quickly, Not necessarily willing to read documents, To understand the attributes and methods of modules.

In order to provide convenience for users, Let them load the module without reading the document, It's going to be used.export default command, Specify the default output for the module.
// export-default.js export default function () { console.log('foo'); }
The above code is a module fileexport-default.js, Its default output is a function.

When other modules load the module,import Command can specify any name for the anonymous function.
// import-default.js import customName from './export-default'; customName();
// 'foo'
Code aboveimport command, Can point to with any nameexport-default.js Output method, In this case, you do not need to know the function name output by the original module. It should be noted that, Thenimport
Command behind, Do not use braces.

export default Command before non anonymous function, It's ok.
// export-default.js export default function foo() { console.log('foo'); } //
Or write it function foo() { console.log('foo'); } export default foo;
In the above code,foo Function name of functionfoo, Invalid outside module. When loading, Load as anonymous function.

Let's compare the default output with the normal output.
// first group export default function crc32() { // output // ... } import crc32 from
'crc32'; // input // Second group export function crc32() { // output // ... }; import {crc32}
from 'crc32'; // input
Two writing methods of the above code, The first group is usingexport default Time, Correspondingimport Statement does not need braces; The second group is not to useexport default Time, Corresponding
import Statement requires braces.

export default Command to specify the default output of the module. Obviously, A module can only have one default output, thereforeexport default Command can only be used once. therefore,import
Do not use braces after commands, Because there is only one way.

In essence,export default Is to output adefault Variable or method of, And then the system allows you to name it anything. therefore, The following is valid.
// modules.js function add(x, y) { return x * y; } export {add as default}; //
Equate to // export default add; // app.js import { default as foo } from 'modules';
// Equate to // import foo from 'modules';
Precisely becauseexport default The command is just an output calleddefault Variables, So it cannot be followed by a variable declaration statement.
// Correct export var a = 1; // Correct var a = 1; export default a; // error export default
var a = 1;
In the above code,export default a The meaning of variablea The value of is assigned to the variabledefault. therefore, The last way to write is to report mistakes.

Similarly, becauseexport default Essentially, the value after the command, Assign todefault Variable defaults later, So write a value directly inexport default after.
// Correct export default 42; // Report errors export 42;
In the above code, The next error is reported because no external interface is specified, In the previous sentence, the external interface is specified asdefault.

Yes.export default command, The input module is very intuitive, Input lodash Module as an example.
import _ from 'lodash';
If you want toimport Statement, Enter default methods and other interfaces at the same time, It can be written as follows.
import _, { each, each as forEach } from 'lodash';
Corresponding to the above codeexport Statements are as follows.
export default function (obj) { // ··· } export function each(obj, iterator,
context) { // ··· } export { each as forEach };
The last line of the above code means, Violent露出forEach接口,默认指向each接口,即forEach和each指向同一个方法.

export default也可以用来输出类.
// MyClass.js export default class { ... } // main.js import MyClass from
'MyClass'; let o = new MyClass();
<>export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起.
export { foo, bar } from 'my_module'; // 等同于 import { foo, bar } from
'my_module'; export { foo, bar };
上面代码中,export和import语句可以结合在一起,写成一行.

模块的接口改名和整体输出,也可以采用这种写法.
// 接口改名 export { foo as myFoo } from 'my_module'; // 整体输出 export * from
'my_module';
默认接口的写法如下.
export { default } from 'foo';
具名接口改为默认接口的写法如下.
export { es6 as default } from './someModule'; // 等同于 import { es6 } from
'./someModule'; export default es6;
同样地,默认接口也可以改名为具名接口.
export { default as es6 } from './someModule';
下面三种import语句,没有对应的复合写法.
import * as someIdentifier from "someModule"; import someIdentifier from
"someModule"; import someIdentifier, { namedIdentifier } from "someModule";
为了做到形式的对称,现在有提案 <https://github.com/leebyron/ecmascript-export-default-from>
,提出补上这三种复合写法.
export * as someIdentifier from "someModule"; export someIdentifier from
"someModule"; export someIdentifier, { namedIdentifier } from "someModule";
<>模块的继承

模块之间也可以继承.

假设有一个circleplus模块,继承了circle模块.
// circleplus.js export * from 'circle'; export var e = 2.71828182846; export
default function(x) { return Math.exp(x); }
上面代码中的export *,表示再输出circle模块的所有属性和方法.注意,export *命令会忽略circle模块的default
方法.然后,上面代码又输出了自定义的e变量和默认方法.

这时,也可以将circle的属性或方法,改名后再输出.
// circleplus.js export { area as circleArea } from 'circle';
上面代码表示,只输出circle模块的area方法,且将其改名为circleArea.

加载上面模块的写法如下.
// main.js import * as math from 'circleplus'; import exp from 'circleplus';
console.log(exp(math.e));
上面代码中的import exp表示,将circleplus模块的默认方法加载为exp方法.

<>跨模块常量

本书介绍const命令的时候说过,const
声明的常量只在当前代码块有效.如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法.
// constants.js 模块 export const A = 1; export const B = 3; export const C = 4;
// test1.js 模块 import * as constants from './constants'; console
.log(constants.A);// 1 console.log(constants.B); // 3 // test2.js 模块 import {A,
B}from './constants'; console.log(A); // 1 console.log(B); // 3
如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下.
// 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'];
然后,将这些文件输出的常量,合并在index.js里面.
// constants/index.js export {db} from './db'; export {users} from './users';
使用的时候,直接加载index.js就可以了.
// script.js import {db, users} from './index';
<>import()

<>简介

前面介绍过,import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适).所以,下面的代码会报错.
// 报错 if (x === 2) { import MyModual from './myModual'; }
上面代码中,引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if
代码块之中毫无意义,因此会报句法错误,而不是执行时错误.也就是说,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if
代码块之中,或在函数之中).

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块.在语法上,条件加载就不可能实现.如果import命令要取代 Node 的require
方法,这就形成了一个障碍.因为require是运行时加载模块,import命令无法取代require的动态加载功能.
const path = './' + fileName; const myModual = require(path);
上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道.import语句做不到这一点.

因此,有一个提案 <https://github.com/tc39/proposal-dynamic-import>,建议引入import()
函数,完成动态加载.
import(specifier)
上面代码中,import函数的参数specifier,指定所要加载的模块的位置.import命令能够接受什么参数,import()
函数就能接受什么参数,两者区别主要是后者为动态加载.

import()返回一个 Promise 对象.下面是一个例子.
const main = document.querySelector('main'); import(`./section-modules/
${someVariable}.js`) .then(module => { module.loadPageInto(main); }) .catch(err
=> { main.textContent = err.message; });
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用.它是运行时执行,也就是说,什么时候运行到这一句,也会加载指定的模块.另外,
import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同.

import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载.

<>适用场合

下面是import()的一些适用场合.

(1)按需加载.

import()可以在需要的时候,再加载某个模块.
button.addEventListener('click', event => { import('./dialogBox.js') .then(
dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ })
});
上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块.

(2)条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块.
if (condition) { import('moduleA').then(...); } else { import('moduleB'
).then(...); }
上面代码中,如果满足条件,就加载模块 A,否则加载模块 B.

(3)动态的模块路径

import()允许模块路径动态生成.
import(f()) .then(...);
上面代码中,根据函数f的返回结果,加载不同的模块.

<>注意点

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数.因此,可以使用对象解构赋值的语法,获取输出接口.
import('./myModule.js') .then(({export1, export2}) => { // ...· });
上面代码中,export1和export2都是myModule.js的输出接口,可以解构获得.

如果模块有default输出接口,可以用参数直接获得.
import('./myModule.js') .then(myModule => { console.log(myModule.default); });
上面的代码也可以使用具名输入的形式.
import('./myModule.js') .then(({default: theDefault}) => { console
.log(theDefault); });
如果想同时加载多个模块,可以采用下面的写法.
Promise.all([ import('./module1.js'), import('./module2.js'), import(
'./module3.js'), ]) .then(([module1, module2, module3]) => { ··· });
import()也可以用在 async 函数之中.
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();