Module Grammar of

<>Module Grammar of

<> 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 Of require,Python Of import, Even CSS All of them @import, 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'); // Equivalent 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 loading fs modular ( Load now fs All methods of ), Generate an object (_fs), Then read from the object 3
Methods . 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 through export Command explicitly specifies the output code , Pass again import Command input .
// ES6 modular import { stat, exists, readFile } from 'fs';
The essence of the above code is from fs Module loading 3 Methods , 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 , More efficient than
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 Grammar of , 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 needed UMD 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 or navigator Object properties .
* Object is no longer required as a namespace ( such as Math object ), In the future, these functions can be provided through modules .
This chapter introduces 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, an error will be reported
* out of commission with sentence
* Cannot assign to read-only property , Otherwise, an error will be reported
* Cannot use prefix 0 Represents octal number , Otherwise, an error will be reported
* Properties that cannot be deleted cannot be deleted , Otherwise, an error will be reported
* Cannot delete variable delete prop, Will report an error , Only attributes can be deleted delete global[prop]
* eval Does not introduce variables in its outer scope
* eval and arguments Cannot be reassigned
* arguments Does not automatically reflect changes in function parameters
* out of commission arguments.callee
* out of commission arguments.caller
* prohibit this Point to global object
* out of commission fn.caller and fn.arguments Get the stack of function calls
* Reserved words added ( such as protected,static and interface)
These restrictions , Modules must comply . Because the strict pattern is ES5 Introduced , Not of ES6, So please refer to the relevant ES5 book , This book is no longer detailed .

among , Special attention this Limitation of .ES6 In the module , top-level this point undefined, That is, it should not be used in the top-level code this.

<>export command

Module function is mainly composed of two commands :export and import.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 use export Keyword output the variable . Here is a
JS file , Used inside export Command output variable .
// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'
;export var year = 1958;
The code above is profile.js file , Saved user information .ES6 Think of it as a module , For inside use export The command outputs three variables externally .

export How to write , 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 in export After command , Use braces to specify a set of variables to output . It and the former ( Directly placed in var
Before statement ) 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 function multiply.

under normal conditions ,export The output variable is the original name , But you can use as Keyword rename .
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as
streamV2, v2as streamLatestVersion };
Use of the above code as keyword , Renamed function v1 and v2 External interface of . After rename ,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 variables m, Or direct output 1.1 It's just a value , Not an interface . The correct way to write it is as follows .
// Style 1 export var m = 1; // Writing method 2 var m = 1; export {m}; // Writing method 3 var n = 1; export
{nas m};
All three of the above are correct , Specified external interface m. Other scripts can use this interface , Get value 1. Their essence is , Between interface name and module internal variables , A one-to-one correspondence is established .

alike ,function and class Output of , 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 code foo, The value is bar,500 Milliseconds later baz.

This is related to CommonJS The specifications are totally different .CommonJS The module outputs a cache of values , There is no dynamic update , See below 《Module Load implementation of 》 A 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 , It's a mistake , Next section import
So is the order . This is because it is in a conditional block of code , You can't do static optimization , Against ES6 Original intention of module design .
function foo() { export default 'bar' // SyntaxError } foo()
In the above code ,export Statement in function , Result reporting error .

<>import command

use export After the command defines the external interface of the module , other JS The file can be passed import Command to load this module .
// main.js import {firstName, lastName, year} from './profile'; function
setName(element) { element.textContent = firstName + ' ' + lastName; }
Code above import command , For loading profile.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 use as keyword , Rename the input variable .
import { lastName as surname } from './profile';
import hinder from 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 of all .
foo(); import { foo } from 'my_module';
The above code will not report an error , because import Execution of is earlier than foo Call to . The essence of this behavior is ,import Commands are executed during the compile phase , Before the code runs .

because import 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 , Variables and if 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 executes lodash modular , But no value is entered .

If you repeat the same sentence multiple times import sentence , Then only once , Not more than once .
import 'lodash'; import 'lodash';
The above code has been loaded twice lodash, But only once .
import { foo } from 'my_module'; import { bar } from 'my_module'; // Equivalent to import
{ foo, bar }from 'my_module';
In the above code , although foo and bar Load in two statements , But they are the same my_module example . in other words ,import Statement is Singleton pattern .

Current stage , adopt Babel transcoding ,CommonJS Modular require Command and ES6 Modular import
command , Can be written in the same module , But it's better not to . because import 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 , Ready to use asterisk (*) Specify an object , All output values are loaded on this object .

Here is a circle.js file , It 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; }
Now? , Load this module .
// main.js import { area, circumference } from './circle'; console.log(' Circular 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(' Circular 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 example above is circle), 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 , use import
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 about 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 file export-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 above import command , Can point to with any name export-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 , At this time import
After command , 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 function foo, 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 // Group 2 export function crc32() { // output // ... }; import {crc32}
from 'crc32'; // input
Two writing methods of the above code , The first group is using export default Time , Corresponding import Statement does not need braces ; The second group is not to use export 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 , therefore export 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 a default 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}; //
Equivalent to // export default add; // app.js import { default as foo } from 'modules';
// Equivalent to // import foo from 'modules';
Because export default The command is just an output called default Variable of , 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 variable a The value of is assigned to the variable default. therefore , The last way to write is to report mistakes .

similarly , because export default Essentially, the value after the command , Assign to default Variable defaults later , So write a value directly in export 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 as default.

Yes export default command , The input module is very intuitive , To input lodash Module as an example .
import _ from 'lodash';
If you want to import In 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 code export The statement is 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();