JS 模块化
背景
JS 本身定位:简单的页面设计 - 页面逻辑简单处理 + 表单提交以及基本动画 并无模块化、命名空间的概念
JS 的模块化需求日益增长
幼年期:无模块化协调阶段
- 开始需要在页面中增加一些不同的 JS:动画、表单、格式化工具
- 多种 JS 文件被分在不同的文件夹中
- 不同的文件被同一个模块所引用
js
// index.html
<script src="js/animation.js"></script>
<script src="js/form.js"></script>
<script src="js/format.js"></script>
<script src="js/main.js"></script>
文件分离最基础的模块化,第一步
script
标签:- async | defer
js
// 1. script 加载 + 解析
// 默认阻塞模式
// 阻塞页面渲染,加载完再渲染。适合优先级较高
<script src="main.js"></script>
// async
// 异步加载,加载完立即执行。适合无优先级的大文件
<script src="main.js" async></script>
// defer
// 延迟模式,加载完等待页面渲染完成再执行。 延迟解析,任何JS都不会阻塞页面渲染,适合优先级较低模块
<script src="main.js" defer></script>
// 2.加载场景:页面加载时,需要加载的JS文件

问题
- 变量名冲突,污染全局作用域;不利于大型项目开发以及多人协作
成长期:模块化的雏形 - IIFE + 立即执行函数
作用域的把控
js
// 定义一个全局变量
let count = 0;
// main.js - 代码块1
const main = () => {
// ......
const count = 1;
return {
default: {
// ......
},
};
};
// tools.js - 代码块2
const tools = () => {
// ......
const count = 2;
return {
// ......
};
};
let mainResult = main();
// mainResult.default
let toolsResult = tools();
// 返回值不冲突就可以使用
-
利用 - 立即执行函数,将代码块包裹
-
IIFE 立即执行函数,将代码块包裹起来,形成独立的作用域,防止变量污染
定义函数 + 立即执行 => 形成一个独立的空间 初步实现一个最简单的模块
js
const module = (() => {
// main.js
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0;
},
};
})();
module.increase();
module.reset();
有额外依赖的时候,如何优化 IIFE
优化:依赖其他模块的 IIFE
js
const module = ((dependModule1,dependModule2) => {
// main.js
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0;
},
};
// ...dependModule1.xxx,dependModule2.xxx
})(dependModule1,dependModule2);
追问
- 深入模块化的多种方案
- 转向框架 - jQuery Vue react
- 转向设计模式 - 注重模块化的设计模式
成熟期
CJS - commonJS
Node.js 制定 特征
- 通过
module
+export
去对外暴露接口 - 通过
require
来调用其他模块 模块组织方式
js
<!---->
// tool.js
// 引入部分
const dependencyModule0 = require("./dependencyModule0");
const dependencyModule1 = require("./dependencyModule1");
// 核心逻辑
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
};
module.exports = {
increase,
reset,
};
// 暴露选择接口
exports.increase = increase;
exports.reset = reset;
<!---->
// main.js
const { increase, reset } = require("./tool.js");
increase();
- 优点 :
CJS
率先在服务端实现了从架构层面解决依赖、全局变量污染的问题 - 缺点 :
CJS
针对服务端,对于异步场景没有很友好地处理和考虑
AMD 规范
通过异步加载 + 允许定制回调函数 - require.js
特征:
js
// 通过 define 来定义模块
define(id, [depends], callback);
require([module], callback);
<!---->
define("amdModule", ["dependencyModule1", "dependency2"], (
dependencyModule1,
dependency2
) => {
// 业务逻辑
let count = 0;
const increase = () => ++count;
// ...dependencyModule1
});
<!---->
require(["amdModule"], (amdModule) => {
amdModule.increase();
});
面试题:兼容判断 AMD & CJS
js
`define` => AMD `exports` =>CJS
define(
"amdModule",
["dependencyModule1", "dependency2"],
(dependencyModule1, dependency2) => {
// 业务逻辑
let count = 0;
const increase = () => ++count;
// ...dependencyModule1
}
)(
typeof module === "object" && module.exports && typeof define !== "function"
? // 是CJS
(factory) => (module.exports = factory(require, exports, module))
: // 是AMD
define
);
// 面试方向:UMD
<!---->
// UMD 实现
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD 如果环境中有define函数,并且define函数具备amd属性,则可以判断当前环境满足AMD规范
define(["jquery"], factory);
} else if (typeof exports === "object" && typeof module === "object") {
// CommonJS规范 node 环境 判断是否支持 module.exports 支持 require 这种方法
module.exports = factory(require("jquery"));
} else {
// 直接挂载在全局对象上
root.returnExports = factory(root.jQuery);
}
})(this, function ($) {
// ...
});
- 优点:解决了浏览器中异步加载模块的问题
- 缺点:有了引入成本,没有考虑按需加载
CMD 工具框架 - sea.js
按需加载
js
define("module", (require, exports, module) => {
let $ = require("jquery");
// ...
let dependencyModule1 = require("./dependencyModule1");
// ...
});
- 优点:按需加载,依赖就近,性能和谐
- 缺憾:依赖于打包,加载逻辑会实际打包到模块中,增加了模块体积
ESM - ES6
新增定义:引入关键字 import
导出关键字 export
js
import dependencyModule1 from "./dependencyModule1";
import dependencyModule2 from "./dependencyModule2";
// 业务逻辑
let count = 0;
const increase = () => ++count;
// ...dependencyModule1
export default {
increase,
};
// 模块核心 公用
ES11
js
import("./dependencyModule1.js").then((dynamicESModule) => {
dynamicESModule.increase();
});
优点 :通过一种最统一的形态整合了 JS 的模块化 缺憾:本质上还是运行时的依赖分析
面试方向:
- 工程化
- ECMAScript