ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
//一个一个导出
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//写成一条语句
export { firstName, lastName, year };
不能直接导出常量,常量导出后无法建立对应关系,这与ES6的模块化设计理念是相通的。
// 报错
export 1;
// 报错
var m = 1;
export m;
//正确
var m = 1;
export {m}
default 限一个 对外相当于变量名default
正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
import from
可以从ES6的模块化设计理念中发现:
import指令会自动提升,
如果处于块级作用域内,就会报错,没法做静态优化(不需要根据条件语句)
module 加载实现默认启用严格模式this为undefined相同模块只会加载一次
default及其他非default一块儿 xxx(default别名),{},因为{}或*(忽略default)
import和export都可以使用as给模块起别名
ES2020提案 引入import()函数,支持动态加载模块。
const main = document.querySelector(‘main’);
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
//(同缺省)异步加载,等到页面页面渲染完,再加载模块
//同步加载,模块加载完成,停止渲染并立即执行,执行结束后,再恢复渲染
CommonsJS输出值拷贝,ES6输出值引用
CommonsJS运行时加载,ES6是编译输出时接口()
CommonsJS的require()是同步加载模块,ES6的import是异步加载,有一个独立的模块依赖的解析阶段
CommonsJS的this指向当前模块,ES6则为undefined
CommonJS 模块输出的是值的缓存,不存在动态更新,
目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。
如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
// 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
js模块包括:ES6模块(ESM),CommonsJS模块(CJS)
Node.js v13.2 默认使用ESM
.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载
.js文件的加载取决于package.json里面type字段的设置。
Package.json
package.json文件有两个字段可以指定模块的入口文件:main和exports。
比较简单的模块,可以只使用main字段,指定模块加载的入口文件。
如果没有type字段,index.js就会被解释为 CommonJS 模块。
// ./node_modules/es-module-package/package.json
{
"type": "module",
"main": "./src/index.js"
}
exports字段的优先级高于main字段。它有多种用法。
由于exports字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js。
(1)子目录别名
package.json文件的exports字段可以指定脚本或子目录的别名。
// ./node_modules/es-module-package/package.json
{
"exports": {
"./submodule": "./src/submodule.js"
}
}
(2)main 的别名
exports字段的别名如果是.,就代表模块的主入口,优先级高于main字段,并且可以直接简写成exports字段的值。
{
"exports": {
".": "./main.js"
}
}
// 等同于
{
"exports": "./main.js"
}
(3)条件加载
利用.这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js 运行的时候,打开–experimental-conditional-exports标志。
{
"type": "module",
"exports": {
".": {
"require": "./main.cjs",
"default": "./main.js"
}
}
}
//等同于
{
"exports": {
"require": "./main.cjs",
"default": "./main.js"
}
}
ES6 模块通过module.createRequire()方法可以加载 CommonJS 模块。但是,这种写法等于将 ES6 和 CommonJS 混在一起了,所以不建议使用。
一个模块同时要支持 CommonJS 和 ES6 两种格式,也很容易。
如果原始模块是 ES6 格式,那么需要给出一个整体输出接口,比如export default obj,使得 CommonJS 可以用import()进行加载。
如果原始模块是 CommonJS 格式,那么可以加一个包装层。
另一种做法是在package.json文件的exports字段,指明两种格式模块各自的加载入口。
ES6 模块的加载路径必须给出脚本的完整路径,不能省略脚本的后缀名。
为了与浏览器的import加载规则相同,Node.js 的.mjs文件支持 URL 路径。
目前,Node.js 的import命令只支持加载本地模块(file:协议)和data:协议,不支持加载远程模块。另外,脚本路径只支持相对路径,不支持绝对路径(即以/或//开头的路径)。
ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node.js 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量(诸如:this、arguments、require)。
“循环加载”(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
通常,“循环加载”表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免出现。
CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值