ES6模块Module
Module 是什么
模块就是把代码拆分成多个文件,每个文件负责一部分功能,然后通过导入和导出实现复用。
一个一个的局部作用域的代码块
什么是模块系统
模块系统需要解决的主要问题:
模块化的问题
消除全局变量
管理加载顺序
Module 的基本用法
导出:分别导出
export const name = '张三';
export function sayHello() { console.log('hello');}导入:
import { name, sayHello } from './user.js';
console.log(name);sayHello();默认导出
export default function add(a, b) { return a + b;}导入时可以随便起名字
import sum from './math.js';
console.log(sum(1, 2));区别:
| 类型 | 导出 | 导入 |
|---|---|---|
| named export | { add } | { add } |
| default export | export default | 随便命名 |
混合导出
export const a = 1;
export default function test() { console.log('default');}导入:
import test, { a } from './file.js';导出方式总结
//直接导出export const a = 1;//先写再导出const b = 2;export { b };//重命名导出const c = 3;export { c as count };import { count } from './file.js';全部导入
import * as utils from './utils.js';
console.log(utils.a);utils.fn();export default 和对应的 import
一个模块可以有多个普通导出(named export),但只能有一个默认导出(default export)。
默认导出:
export default function add(a, b) { return a + b;}//导入// app.jsimport add from './math.js';console.log(add(1, 2));//3为什么导入时没有 {}? 因为导出的是这个模块的默认值,导入时直接拿,名字可以随便起
//import 任意名字 from '模块';import add from './math.js';import fn from './math.js';默认导出对象:
export default { add, sub, mul};
import math from './math.js';math.add();默认导出类:
export default class User { login() {}}
import User from './User.js';const u = new User();默认导出匿名函数:
export default function () { console.log('hello');}import fn from './a.js';fn();默认导出匿名类
export default class { login() {}}import User from './User.js';const u = new User();export 和对应的 import
一个模块可以有多个普通导出,普通 export 是“按名字导出”,导入时必须按名字取。
普通导出:
//导出export function add() {}export function sub() {}//导入 名字必须对应。import { add, sub } from './math.js';import { fn } from './math.js';//错误
function add(a, b) { return a + b;}function sub(a, b) { return a - b;}function mul(a, b) { return a * b;}
export { add, sub, mul};为什么要加 {}?因为导出了多个名字,导入时必须明确
导出后再统一 export
const a = 1;const b = 2;export { a, b };//等价于export const a = 1;export const b = 2;导出时起别名
const a = 1;
export { a as count};//导入肯定就需要别名import { count } from './a.js';console.log(count);-
导入时起别名
import { count as c } from './a.js';console.log(c); -
一次导入导出多个
export const a = 1;export const b = 2;export const c = 3;//导入import { a, b, c } from './a.js'; -
全部导入:
import * as utils from './a.js'; -
导入导出复合写法
//复合写法无法在当前模板中使用export { age } from './module.js';//类似中转站
Module 的注意事项
import 必须写在顶层
//错误if (true) { import { a } from './a.js';}function fn() { import { a } from './a.js';}//正确import { a } from './a.js';//如果需要条件加载:if (true) { const module = await import('./a.js');}import 导入的是只读绑定:
export let count = 1;//b.jsimport { count } from './a.js';count = 2; // ❌import 得到的是只读引用。不能修改导出的变量是动态绑定:
export let count = 1;
setTimeout(() => { count = 2;}, 1000);//b.jsimport { count } from './a.js';setTimeout(() => { console.log(count);//2}, 2000);一个模块只能有一个 default
默认导出导入时名字随便起
普通导出必须按名字导入
导入的名字不能重复声明
import { a } from './a.js';let a = 2;//error Identifier 'a' has already been declared
//解决,起一个别名import { a as importedA } from './a.js';let a = 2;模块默认是严格模式
模块有自己的作用域,模块之间,变量默认不共享
let a = 1;//b.jsconsole.log(a);//a is not defined模块只执行一次
import './a.js';import './a.js';
console.log('执行');浏览器必须加 type="module"
<script type="module" src="./app.js"></script>Node ESM 必须开启,或者,文件拓展名字.mjs,注意node12之前存在兼容问题
{ "type": "module"}Node ESM 必须写扩展名
import { add } from './math';//错误import { add } from './math.js';import 有提升:import 的“提升”指的是模块初始化阶段提前建立绑定,不是代码文本位置上的变量提升。
console.log(a);import { a } from './a.js';//看起来:先使用后导入;实际上:import 在模块初始化阶段就完成了。循环引用要小心:如a导入b文件,b文件导入a文件
import { b } from './b.js';export const a = 1;//b.jsimport { a } from './a.js';export const b = 2;//Cannot access before initializationModule的应用
最常见的应用场景
| 场景 | 使用方式 |
|---|---|
| 工具函数 | export |
| 常量 | export |
| API | export |
| 配置 | export default |
| 类 | export default |
| 组件 | export default |
| 数据库 | export |
| 路由 | export |
API 请求模块:
export function request(url) { return fetch(url).then(res => res.json());}//api/user.jsimport { request } from './request.js';
export function getUser() { return request('/user');}配置文件模块:
export default { baseURL: 'http://localhost:3000', timeout: 5000};//app.jsimport config from './config.js';console.log(config.baseURL);拓展-ES Module 和 CommonJS 的区别
- ES Module(ESM)是 JavaScript 官方标准模块规范;CommonJS(CJS)是 Node.js 最早使用的模块规范。
语法区别
CommonJS:
module.exports = { add, sub};const math = require('./math');const { add } = require('./math');ES Module:
export function add() {}export function sub() {}
import { add, sub } from './math.js';加载时机不同
CommonJS:运行时加载
const math = require('./math');//代码运行到这里才去加载模块
//合法if (true) { const a = require('./a');}ES Module:编译时加载(静态加载)
import { add } from './math.js';//代码执行之前就已经确定依赖关系//报错if (true) { import { a } from './a.js';}//动态导入const module = await import('./a.js');值拷贝 VS 引用绑定
CommonJS:值拷贝
let count = 1;module.exports = { count};count = 2;//b.jsconst obj = require('./a');
console.log(obj.count);//1 导出时已经复制了一份值。ES Module:动态绑定
export let count = 1;
setTimeout(() => { count = 2;}, 1000);//b.jsimport { count } from './a.js';
setTimeout(() => { console.log(count);//2 import 得到的是引用绑定,不是值拷贝。}, 2000);this 指向不同
CommonJS
console.log(this);//module.exportsES Module
console.log(this);//undefined ES Module 默认严格模式。总结:
| 区别 | CommonJS | ES Module |
|---|---|---|
| 提出者 | Node.js | ECMAScript 官方 |
| 导出 | module.exports | export |
| 导入 | require | import |
| 加载时机 | 运行时 | 编译时 |
| 值传递 | 值拷贝 | 动态绑定 |
| this | module.exports | undefined |
| 顶层 await | ❌ | ✅ |
| Tree Shaking | ❌ | ✅ |
| 浏览器支持 | ❌ | ✅ |
| 静态分析 | ❌ | ✅ |
| 文件名 | .js .cjs | .mjs |
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














