浏览器实现的模块化,成为浏览器和服务器通用的模块解决方案。
nodejs esm
nodejs默认把js文件默认当成commonjs文件加载,可以通过添加.mjs后缀修改。
.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。
package.json
{
"type":"module"
}
浏览器
scrpit标签加 type="module"属性。
利用顶层的this等于undefined这个语法点,可以侦测当前代码是否在 ES6 模块之中。
const isNotModuleScript = this !== undefined;
<script type="module" src="./foo.js"></script>
相当于
<script defer src="./foo.js"></script>
ES6 esm
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。
这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高
优势
- 1.不再需要命名空间
- 2.解决全局变量问题
- 3.静态化,打包工具tree shaking
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
export 命令
export const a = 1
export const b = 1
等同于
const a = 1
const b = 1
export { a, b }
-
export 的值是引用,例如
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
-
export的内容不能再其它块级作用域内
function a() {
export {}
}
//报错
import命令
- 1.import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
- 2.import具有提升效果(这种行为的本质是,import命令是编译阶段执行的)
foo();
import { foo } from 'my_module';
//不会报错
- 3.export default提供默认输出(相当于默认匿名的引用)
模块的继承
export *命令会忽略继承模块的default方法
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
- 4.import不支持放在其它模块内,例如
if (x === 2) {
import MyModual from './myModual';
}
ES2020 提案支持动态import,import()返回一个 Promise 对象,类似require(import是异步,require是同步)
之所以是异步猜测可能是考虑浏览器只执行必要的文件,打包工具可以把依赖关系确定而不需要把整个文件引入,再需要的时候再创建script异步加载,类似于动态路由的实现
ESModule的加载
- 1.传统script加载会阻塞css tree和dom tree的构建
- 2.async 告诉浏览器该脚本不会阻塞页面构建,标记脚本内不会操作dom
- 3.defer告诉浏览器立即下载 直到解析完毕在执行
有多个defer脚本,会按照它们在页面出现的顺序加载,多个async脚本是不能保证加载顺序