# 【JavaScript进阶】从import和require到JS模块化

# 一、JavaScript模块标准

在JavaScript中,存在两种模块化标准。第一种是ES6模块化标准(以下简称ESM),另一种则是CommonJS规范(以下简称CJS)。

⚠️二者互不兼容。

其中CJS最早出现。在此之前,前端没有模块化概念,在各种插件中的变量命名容易污染全局作用域,简直就是灾难。


import/exportrequire/exports分属于ESMCJS标准,它们有很大的区别。

# 1.CJS

CJS先于ESM被提出,CJS诞生之初是为了在浏览器环境之外的地方使用模块化的JavaScript。

CJS中,每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。require方法用于加载模块。 —— 阮一峰

Node采用CJS规范,所以我们会发现CJS模块通常需要Node环境的支持,换句话说,它需要服务器环境的支持。

要点

  • 1.Node支持CJSESM,参考esm和cjs模块的相互加载
  • 2.CJS不能在浏览器中运行,但可以将其转换成ESM后在浏览器中运行。

# 2.ESM

ESM是ECMAScript标准的模块化标准,在ES6中被正式提出。在此之前的CJS标准应用场景是服务端环境,ESM则是用于浏览器环境

要点

  • 1.ESM加载时属于静态加载,可以在加载时进行Tree Shaking,即树摇,去除模块中引入了却未被使用的方法,以减小打包后的代码体积,获得更快的加载速度。

  • 2.ESM可以直接在现代浏览器中运行。例👇:

    <script type="module">
        ...
    </script>
    
    1
    2
    3

# 3.二者区别

  • 如何简单区分二者:👇

    • CJSrequire/exports
    • ESMimport/export
  • 加载时的区别:

    CJS属于运行时加载(动态加载),理论上可以放在代码的任何地方CJS在加载时会加载模块内的所有的东西,使用其中的一种或几种。而ESM属于编译时加载(静态加载),必须放在代码顶部,它可以在编译时就完成模块加载,速度比较快。

为何动态加载不能进行Tree Shaking,只有静态加载时可以?

CJS属于运行时加载,可以在代码中的任何地方引入,如果有以下代码片段:

if (flag) {
    myModule = require("a_module_path");
} else {
    myModule = require("b_module_path");
}
1
2
3
4
5

由于无法确定用到了哪个模块,所以无法进行Tree Shaking


CJS不同的是ESM中所有模块必须在代码顶部声明引入。

import AModule from "a_module_path";
import BModule from "b_module_path";
1
2

# 二、import/exportrequire/exports的使用

# 1.import/export

// module
export default func;
export const func;
export function func;
export { func1, func2 };
export * from "other_module_path";

// main
import func from "module";
import { newFunc as func } from "module";
import * as funcModule from "module";
import { func } from "module";
import module, { func } from "module";
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2.require/exports

// module
module.exports = {
  func: function() {}
}

// main
const funcModule = require("module");// 全部引入
funcModule.func()
1
2
3
4
5
6
7
8

# 三、ESMCJS模块的相互加载

⚠️注意

值得注意的是,两种模块的相互加载需要在Node环境下,因为CJS模块不能在浏览器中运行

# 1.在ESM中引入CJS模块

import可以加载CJS模块,但是必须是整体加载,否则会报错。👇

// module 为CJS模块
import module from "module";// √
import { func } from "module";// ×

// 部分加载
import module from "module";
const { func } = module;
1
2
3
4
5
6
7

Node环境中使用import加载CJS模块时,Node会将CJS模块的module.exports输出的结果作为默认输出。即module.exports等于export dafult。👇

// CJS module
module.exports = {
  a: "a",
  b: "b"
}

// main
import module from "module";// module: {a: "a", b: "b"}
import { default as module } from "module";// module: {a: "a", b: "b"}
1
2
3
4
5
6
7
8
9

# 2.在CJS中引入ESM模块

// ESM module
export default {
  func() {
    console.log("fun!")
  }
}

// main
const module = require("module");// ×
// {default: {...}, ...}
const module = require("module").default;// √
1
2
3
4
5
6
7
8
9
10
11

# 四、浏览器如何加载CJS

上面一节的ESM和CJS模块的相互加载实际上讲的是如何在Node环境加载ESM,那么浏览器环境下如何加载CJS

答案是转格式。。详见参考资料[4]

# 五、参考资料

Last Updated: 2 years ago