JS 模块化
1.历史
背景
JS 本身是为了简单的页面设计:页面动画,表单提交,并不会内置命名空间或者模块化的概念
随着业务的飞速发展,针对 JS 模块化涌现了大量的解决方案
幼年期:无模块化
- 开始需要在页面中增加不同的 js 文件,如:动画.js、验证.js、格式化.js
- 多种 JS 为了维护和可读性,被分在了不同的 js 文件里
- 不同的模块在同一个模板中被引用
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="deps1.js"></script>
<script src="deps2.js"></script>
对此表示认可:相比较使用一个 js 文件,这种多个 JS 文件的实现是最基础的 JS 模块化思想,是进步的 问题:污染全局作用域,每一个代码块都暴露在全局,协调每一个代码块中的变量函数不能相同,不利于大型项目的开发和维护
成长期:模块化的雏形 --- IIFE 立即执行函数(语法测的优化)
作用域的把控
例子:
// 定义全局变量 count.js
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
- 独立作用域-函数
- 利用函数作用域
// 定义全局变量 count.js
() => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
};
- 仅定义了一个函数,并未执行
- 解决方案:立即执行
// 定义全局变量 count.js
() => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
}();
- 定义一个简单的模块:完成一个模块的封装,实现了对外暴露功能,保留变量,不污染全局作用域的能力
// 定义全局变量 count.js
const iifeCountModule = (() => {
let count = 0;
return {
increase : () => ++count,
reset : () => {
count = 0;
console.log("count is reset");
};
}
})();
优化 1:如果要依赖其他模块?--- 传参依赖
// 定义全局变量 count.js
const iifeCountModule = ((dependenceModule1,dependenceModule2) => {
let count = 0;
// dependenceModule1,dependenceModule2
return {
increase : () => ++count,
reset : () => {
count = 0;
console.log("count is reset");
};
}
})(ependenceModule1,dependenceModule2);
** 面试题:了解早期的 jquery 的依赖处理和模块加载方案吗? | JS 中如何实现私有变量模块? 答案: IIFE + 传参调配 **
成熟期
CJS module:CommonJS
特征:
- 通过 module + exports 来进行模块的输出定义
- require 来调用其他模块
// 定义全局变量 count.js
const dependenceModule1 = require("./dependenceModule1.js");
const dependenceModule2 = require("./dependenceModule2.js");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
exports.increase = increase;
module.exports = {
increase,
reset,
};
模块使用方式
// main.js
const { increase, reset } = require("./count.js");
increase();
reset();
const commonJsCountModule = require("./count.js");
commonJsCountModule.increase();
commonJsCountModule.reset();
- 优点:CommonJs 规范在服务器端率先完成了 JS 的模块化,解决了依赖、全局变量污染的问题,这也是 JS 能够在服务器端运行的必要条件
- 缺点:优先考虑同步场景,在浏览器端所有文件异步拉取的场景中,适用性差
AMD 规范
非同步加载模块,允许制定回调函数
新增定义方式:
require([module], callback);
define(id, [depends], callback); // 模块名,依赖模块,工厂方法
// 加载完依赖,再执行回调函数
define("amdCountModule", ["dependenceModule1", "dependenceModule2"], (
dependenceModule1,
dependenceModule2
) => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
return {
increase,
reset,
};
});
引入模块:
require(["amdCountModule"], (amdCountModule) => {
amdCountModule.increase();
amdCountModule.reset();
});
- 面试题:如果想在 AMD 中使用 require 来加载同步模块 -- AMD 支持向前兼容
define((require) => {
const dependenceModule1 = require("./dependenceModule1.js");
const dependenceModule2 = require("./dependenceModule2.js");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
return {
increase,
reset,
};
});
- 面试题:有没有方式可以同时兼容 AMD & Common => UMD 实现机制:
(define=>define((require,exports,module)=>{
const dependenceModule1 = require("./dependenceModule1.js");
const dependenceModule2 = require("./dependenceModule2.js");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("count is reset");
};
exports.increase = increase;
module.exports = {
increase,
reset,
};
}))(
typeof module ==='object'
&& module.exports
&& typeof define !== 'function'
// commonJS
? factory => module.export=>factory(require,exports,module)
// AMD
: define
);
- 优点:适合在浏览器环境中异步加载模块,并行加载多个模块。向前兼容工厂函数中使用 commonJS 规范 缺点:提高了开发成本,不能按需加载,而是必须提前加载所有的依赖
CMD 规范 -SeaJS -按需加载
define((require, exports, module) => {
const $ = require("./jquyer.js");
// .....
const module1 = require("./module1.js");
// .....
});
- 优点:按需加载,依赖就近
- 缺点:依赖打包,依赖逻辑存在于每一个模块中
ESM 模块化
export 导出关键字 import 导入关键字
import dependenceModule1 from "./dependenceModule1.js";
import dependenceModule2 from "./dependenceModule2.js";
let count = 0;
export const increase = () => ++count;
export const reset = () => {
count = 0;
console.log("count is reset");
};
export default {
increase,
reset,
};
模块引入
<script type="module" src="./count.js"></script>;
import { increase, reset } from "./count.js";
increase();
reset();
import esmCountModule from "./count.js";
esmCountModule.increase();
esmCountModule.reset();
ES11 原生异步解决方案
import("./count.js").then(({ increase, reset }) => {
increase();
reset();
});