ES6模块化详解:export与import语法、循环依赖及浏览器兼容

作者:闭包旅人

时间:2026-10-22

浏览:578

本文详解ES6模块化机制,涵盖export与import的六种导出语法、默认导出及通配符用法。重点分析模块引用的动态特性与循环依赖导致的静态解析问题,对比RequireJS与Node.js处理差异,并提供Babel编译方案以解决浏览器兼容性问题。

文章目录

ES6模块化背景与基础

在ES6标准推出之前,前端开发者主要依赖RequireJS或SeaJS等库来实现模块化开发。其中,RequireJS遵循AMD(异步模块定义)规范,而SeaJS则基于CMD(通用模块定义)规范。这两种工具旨在推动前端模块化的普及,关于AMD与CMD的具体区别,可参考后续提供的相关链接。

ES6标准原生引入了模块化支持,这是JavaScript语言首次内置module特性。随着浏览器环境的完善,开发者可以直接在浏览器中使用import和export语句来导入和导出各个模块,此时每个JavaScript文件即被视为一个独立的JS模块。

目前主流浏览器对原生模块的支持程度存在差异,因此实际开发中通常使用BabelJS或Traceur等工具,将ES6代码编译转换为兼容ES5版本的JavaScript代码,以确保跨浏览器环境的正常运行。

ES6模块化基本规则

ES6模块化遵循以下基本规则与特性:

补充ES6模块化的基本规则与特性:

1:每个模块仅加载一次,对应的JavaScript文件只执行一次。若后续再次加载同一目录下的相同文件,系统将直接从内存中读取结果。这意味着每个模块本质上是一个单例对象。

2:每个模块内部声明的变量均为局部变量,不会污染全局作用域,从而避免了命名冲突。

3:模块内部的变量或函数可以通过export语句进行导出,供其他模块使用。

4:一个模块可以通过import语句导入其他模块的功能。

运行以下示例代码:


//lib.js
//导出常量
export const sqrt = Math.sqrt;
//导出函数
export function square(x) {
  return x * x;
}
//导出函数
export function diag(x, y) {
  return sqrt(square(x) + square(y));
}

//main.js
import { square, diag } from './lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5



ES6模块化背景与基础

export与import语法详解

以下是几种基本的import和export语法示例:

第一种导出方式:

在lib.js文件中,使用export{接口}语法导出接口,大括号内的接口名称需与之前定义的变量名一致,import和export语句在此处形成对应关系。

运行以下示例代码:


//lib.js 文件
let bar = "stringBar";
let foo = "stringFoo";
let fn0 = function() {
  console.log("fn0");
};
let fn1 = function() {
  console.log("fn1");
};
export{ bar , foo, fn0, fn1}

//main.js文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();

ES6模块化背景与基础

第二种导出方式:

在使用export导出接口时,可以使用XX as YY语法修改导出的接口名称。例如将closureFn重命名为sayingFn,以便通过名称直观理解其功能,无需查阅文档。

运行以下示例代码:


//lib.js文件
let fn0 = function() {
  console.log("fn0");
};
let obj0 = {}
export { fn0 as foo, obj0 as bar};

//main.js文件
import {foo, bar} from "./lib";
foo();
console.log(bar);

ES6模块化背景与基础

第三种导出方式:

这种方式允许直接在export语句中定义要导出的函数或变量,无需先声明再导出。

运行以下示例代码:


//lib.js文件
export let foo = ()=> {console.log("fnFoo") ;return "foo"},bar = "stringBar";

//main.js文件
import {foo, bar} from "./lib";
console.log(foo());
console.log(bar);

ES6模块化背景与基础

第四种导出方式:

这种导出方式无需关注变量名,相当于匿名导出,直接将开发好的接口通过export输出。

如果一个JavaScript模块文件仅包含单一功能,则可以使用export default进行默认导出。

运行以下示例代码:


//lib.js
export default "string";

//main.js
import defaultString from "./lib";
console.log(defaultString);

ES6模块化背景与基础

第五种导出方式:

export同样可以默认导出函数。在import导入时,名称可以任意指定,因为每个模块仅允许有一个默认接口。

运行以下示例代码:


//lib.js
let fn = () => "string";
export {fn as default};

//main.js
import defaultFn from "./lib";
console.log(defaultFn());

第六种导出方式:

使用通配符*可以重新导出其他模块的接口(类似于转载文章但未注明出处)。

运行以下示例代码:


//lib.js
export * from "./other";
//如果只想导出部分接口, 只要把接口名字列出来
//export {foo,fnFoo} from "./other";

//other.js
export let foo = "stringFoo", fnFoo = function() {console.log("fnFoo")};

//main.js
import {foo, fnFoo} from "./lib";
console.log(foo);
console.log(fnFoo());

ES6模块化背景与基础

其他补充:ES6的import和export提供了丰富的导入及导出语法选项。

在import时,可以使用通配符*导入整个外部模块:

运行以下示例代码:


import * as obj from "./lib";
console.log(obj);

模块引用的动态特性

ES6导入的模块属于引用类型,而非值拷贝。

每个导入的JavaScript模块都是“活的”,每次访问该模块的变量或函数时,获取的都是最新值。这是原生ES6模块与AMD和CMD规范的区别之一。以下代码修改自http://exploringjs.com/es6/ch_modules.html#_imports-are-read-only-views-on-exports。

运行以下示例代码:


//lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}
export function setCounter(value) {
  counter = value;
}


//main.js
import { counter, incCounter ,setCounter} from './lib';

// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4
setCounter(0);
console.log(counter); // 0

在main.js中,counter始终指向lib.js中的局部变量counter。按照JavaScript的常规行为,数字、字符串或布尔值等原始类型通常被复制而非赋址,但模块导出机制例外。

循环依赖问题与处理

关于循环依赖的问题:

Node.js处理循环依赖的方式如下:打开。

循环依赖是JavaScript模块化开发中常见的问题。在浏览器端,使用RequireJS测试模块化时,若file0.js依赖于file1.js,而file1.js又依赖于file0.js,则需确定file0.js和file1.js的执行顺序。

运行以下示例代码:


//index.html
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta charset="utf-8"/>
</head>
<body>

<script data-main="cyclic" src="https://cdn.bootcss.com/require.js/2.2.0/require.min.js"></script>
<script>
//cyclic.js
require(["file0"], function(file0) {
  console.log(file0)
})

//file0.js
define(["file1"], function(file1) {
  console.log(file1)
  return {
    file0 : "file0"
  }
})

//file1.js
define(["file0"], function(file0) {
  console.log(file0);
  return {
    file1 : "file1"
  }
})
</script>
</body>
</html>
 

控制台依次输出结果为:

运行以下示例代码:

undefined Object { file1: "file1" } Object { file0: "file0" }

在执行file1.js时,file0.js尚未执行完毕,因此输出undefined。这种输出结果与Node.js的情况一致。

随后使用司徒大神的mass-framework框架进行测试,该框架直接提示:“模块与之前的某些模块存在循环依赖”,这种处理方式更为友好。相比之下,RequireJS直接执行循环依赖的模块,容易在开发过程中引发潜在问题。

接下来在babel-node环境下进行测试:以下是几个测试用例,可忽略。

使用ES6模块进行测试,只要模块被引用,无论模块是否执行完毕,该模块的export已被导出。若导出的是函数:

运行以下示例代码:


//cyclic.js
import fn0 from "./file0";
fn0();

//file0.js
import fn1 from "./file1";
fn1();
console.log("file0.js runs");
export default function() {console.log("file0 export runs")}

//file1.js
import fn0 from "./file0";
fn0();
console.log("file1.js runs");
export default function() {console.log("file1 export runs")}

ES6模块化背景与基础

若导出的是字符串:

运行以下示例代码:


//cyclic.js
import str from "./file0";
console.log(str);

//file0.js
import str1 from "./file1";
console.log(str1)
console.log("file0.js runs");
export default "str0";

//file1.js
import str0 from "./file0";
console.log(str0)
console.log("file1.js runs");
export default "str1";

ES6模块化背景与基础

若导出的是对象:

第一行会先输出一个初始值{},在等待file0.js和file1.js执行完毕后,才输出file0.js导出的对象。

若导出的是数组:

第一行会输出一个经静态分析后的初始值undefined,在等待file0.js和file1.js执行完毕后,才输出file0.js导出的对象。

若导出的是布尔值:

第一行会输出一个经静态分析后的初始值undefined,在等待file0.js和file1.js执行完毕后,才输出file0.js导出的布尔值。

产生上述现象的原因可在http://exploringjs.com/es6/ch_modules.html#_modules找到答案:ES6的import和export被提升至JavaScript代码的最顶层,在函数、对象或基本值被导出时提前进行静态分析。参考:http://www.ecma-international.org/ecma-262/6.0/#sec-parsemodule , http://www.ecma-international.org/ecma-262/6.0/#sec-toplevelmoduleevaluationjob。

结论:使用ES6的export导出数据接口时,建议统一使用函数,以避免在循环依赖场景中,因JavaScript将不同类型的对象静态解析为不同初始值而引发的问题。

浏览器兼容性说明

浏览器兼容情况:

  1. Chrome浏览器目前不支持import和export。
  2. Firefox的支持也有限,但比Chrome稍好。
  3. 建议使用Babel进行编译处理。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章