【ES6】Moudle
概述
ES6之前JS没有模块系统,社区方案有CommonJS(用于服务器)和AMD(用于浏览器)
// CommonJS模块,运行时加载,只有运行时才能得到这个对象,导致完全无法在编译时做静态优化
let fs = require('fs');
ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。而上述两者方案只有在运行时才能确定。
// ES6模块,编译时已确定,效率更好
import { stat, exists, readFile } from 'fs';
当然,这也导致了没法引用 ES6 模块本身,因为它不是对象
ES6模块采用严格模式
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop,会报错,只能删除属性delete global[prop] eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化- 不能使用
arguments.callee - 不能使用
arguments.caller - 禁止
this指向全局对象,ES6模块中this指向undefined - 不能使用
fn.caller和fn.arguments获取函数调用的堆栈 - 增加了保留字(比如
protected、static和interface)
export命令
export命令用于规定模块的对外接口
// 输出变量、函数或类 export let a = 'lpr'
export function fn() {}
或
let a = 111
function fn(){}
export {a,fn}
可用as关键字重命名
function v1() { ... }
export {
v1 as streamV1,
}
- export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
- export语句输出的接口与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
- export命令可以出现在模块的任何位置,只要处于模块顶层就可以
- commonJS模块输出的是值得缓存,不存在动态更新
import命令
import命令用于输入其他模块提供的功能
import { a,b,c } from './profile.js';
为输入的变量重命名使用as关键字
import { lastName as surname } from './profile.js';
- import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面改写接口。
- import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
- import命令具有提升效果,会提升到整个模块的头部,首先执行
- import是静态执行,所以不能使用表达式和变量
模块的整体加载
import * as circle from './circle';
export default 命令
为模块指定默认输出。
// 该处默认输出一个匿名函数
export default function () { console.log('foo'); }
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
import customName from './export-default'; customName(); // 'foo'
比较一下默认输出和正常输出。
// 默认输出
export default function crc32() {}
import crc32 from 'crc32';
// 正常输出
export function crc32() {};
import {crc32} from 'crc32';
export 与 import 的复合写法
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
- 但需要注意的是,写成一行以后,
foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar - 可以实现模块的继承
跨模块常量
如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
// 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
浏览器加载
传统方法
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>
defer与async的区别是:
defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序
浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。
<script type="module" src="./foo.js"></script>
异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
Node.js 加载
有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。
Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"。
如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。

浙公网安备 33010602011771号