ES6模块Module详解:import、export与加载机制对比

作者:函数茶馆

时间:2026-10-22

浏览:81

本文深入解析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程序设计有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章