ES6模块Module详解:import、export与加载机制对比
本文深入解析ES6 Module模块的用法,涵盖import与export命令的具体语法、默认导出export default的使用方式,以及整体加载策略。重点对比ES6静态加载与CommonJS动态加载在值引用、缓存机制及模块实例共享方面的核心差异,帮助开发者掌握现代JavaScript模块化编程。
本文详细解析ES6新增的Module模块功能,提供具体应用示例,供开发者参考学习。
Module简介
一、Module简介
ES6中的Class仅是对ES5构造函数原型链继承语法的封装,并未解决模块化问题。Module功能的引入正是为了解决这一痛点。
在JavaScript发展历史上,长期缺乏原生的模块(module)体系,导致大型程序难以拆分为相互依赖的小文件进行组装。这一功能在其他编程语言中早已普及。
ES6发布前,社区主要制定了CommonJS和AMD两种模块加载方案,分别服务于服务器端和浏览器端。ES6在语言规范层面实现了模块功能,其实现简洁且通用,旨在取代现有的CommonJS和AMD规范,成为浏览器与服务端通用的模块标准。
ES6模块的设计核心在于静态化,旨在编译阶段即可确定模块的依赖关系(即“编译时加载”)以及输入输出的变量。相比之下,CommonJS和AMD模块均需在运行时才能确定这些内容。
浏览器中引入ES6模块的语法如下。
<script type="module" src="fs.js"></script>
上述代码在网页中引入了一个名为fs.js的模块,通过设置type属性为module,浏览器即可识别其为ES6模块。
// ES6加载模块
import { stat, exists, readFile } from 'fs';
上述代码通过import命令加载Module,并引入其中特定的方法。
import 和 export
二、import 和 export
模块功能主要由export和import两个命令构成。export命令用于定义模块对外提供的接口,而import命令用于引入其他模块提供的功能。
每个模块对应一个独立文件,文件内部的变量默认对外不可见。若需让外部读取模块内部的特定变量,必须使用export关键字进行输出。以下JS文件展示了使用export命令输出变量的示例。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
export的写法除上述方式外,还有另一种推荐形式(这种方式便于在脚本尾部清晰查看所有输出变量)。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export命令除了输出变量,还可输出函数或类(class)。通常,export输出的变量名即为原变量名,但可使用as关键字进行重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
通过export命令定义模块对外接口后,其他JS文件即可通过import命令加载该模块(文件)。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上述代码中的import命令用于加载profile.js文件,并从中引入变量。import命令接收一个对象(使用大括号表示),指定需从其他模块导入的变量名。大括号内的变量名必须与被导入模块(profile.js)对外接口的名称完全一致。
若需为引入的变量重新命名,import命令需使用as关键字进行重命名操作。
import { lastName as surname } from './profile';
import命令具有提升效果,会被提升至整个模块的头部优先执行。
foo();
import { foo } from 'my_module';
模块的整体加载
三、模块的整体加载
除指定加载某个输出值外,还可使用整体加载方式,即通过星号(*)指定一个对象,将所有输出值加载至该对象上。
假设存在一个circle.js文件,其输出了area和circumference两个方法。
现需加载该模块。
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上述写法是逐一指定需加载的方法,整体加载的写法如下。
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default
四、export default
为提升用户便利性,使其无需查阅文档即可加载模块,可使用export default命令为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
上述代码是一个名为export-default.js的模块文件,其默认输出为一个函数。
其他模块加载该模块时,import命令可为该匿名函数指定任意名称。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
需注意,此时import命令后不使用大括号。
本质上,export default输出一个名为default的变量或方法,系统允许为其指定任意名称。其后不能跟随变量声明语句。
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
ES6模块加载的实质
五、ES6模块加载的实质
ES6模块的加载机制与CommonJS模块截然不同。CommonJS模块输出的是值的拷贝,而ES6模块输出的是值的引用。
CommonJS模块输出的是被输出值的拷贝,一旦输出值,模块内部的变化便不再影响该值。以下lib.js模块文件示例说明了这一点。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上述代码输出了内部变量counter及修改该变量的内部方法incCounter。随后,在main.js中加载该模块。
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
上述代码表明,lib.js模块加载后,其内部变化不再影响输出的mod.counter。这是因为mod.counter属于原始类型的值,会被缓存。除非将其写为函数形式,否则无法获取内部变动后的值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
上述代码中,输出的counter属性实际上是一个取值器函数。此时再执行main.js,即可正确读取内部变量counter的变动情况。
ES6模块的运行机制与CommonJS不同,遇到模块加载命令import时,仅生成一个动态的只读引用,并不立即执行模块。直到真正需要使用该值时,才从模块中获取。换言之,ES6的输入类似于Unix系统的“符号连接”,原始值发生变化时,import输入的值也会随之改变。因此,ES6模块采用动态引用,不缓存值,模块内的变量绑定其所在的模块。
沿用上述示例。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
上述代码表明,ES6模块输入的变量counter是动态的,完全反映其所在模块lib.js内部的变化。
由于ES6输入的模块变量仅为“符号连接”,因此该变量是只读的,尝试重新赋值将导致报错。
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
上述代码中,main.js从lib.js引入变量obj,可对obj添加属性,但重新赋值会报错。因为变量obj指向的地址是只读的,不可重新赋值,这类似于main.js中创建了一个名为obj的const变量。
最后,export通过接口输出的是同一个值。不同的脚本加载该接口,得到的均是相同的实例。
// mod.js
function C() {
this.sum = 0;
this.add = function () {
this.sum += 1;
};
this.show = function () {
console.log(this.sum);
};
}
export let c = new C();
上述脚本mod.js输出的是C的一个实例。不同脚本加载该模块,得到的都是同一个实例。
// x.js
import {c} from './mod';
c.add();
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';
执行main.js后输出1,这证明了x.js和y.js加载的都是C的同一个实例。
希望本文所述内容对ECMAScript程序设计有所帮助。
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
