浅谈模块化
什么是模块
- 将一个复杂的程序按照一定规则封装成几个块,并进行组合在一起;
- 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;
为什么要使用模块化
- 私有的作用域,避免命名冲突,减少命名空间污染
- 更好的分离, 按需加载
- 更高复用性
- 高可维护性
模块化的发展历史
函数时期
function fn() {}
命名空间时期:模块化的思路雏形初显
var student = {
name: "luyi",
getScore: function () {},
};
student.name;
student.getScore;
闭包时期
// 递增的小栗子
function test() {
let count = 1;
return function () {
count++;
console.log(count);
};
}
var a = test();
a();
解决依赖问题:此处已有现代模块化(vue)的感觉了,模块各自独立,模块之间有依赖(global, $),逻辑层操作视图层
(function (global, $) {
var name = "Chenluo Notes";
let data = "www.baidu.com";
// 操作数据的函数
function doSth() {
//用于暴露有函数
console.log(`foo() ${data}`);
$("body").css("background", "red");
}
function getName() {
return name;
}
global.student = { name, doSth, getName };
})(window, jquery);
console.log(window.student);
CJS 规范
var module = {
exports: {},
}(function (modules, exports, require) {
const $ = require("../juery.min.js");
var name = "Chenluo Notes";
function getName() {}
modules.exports = { name, getScore };
exports.name = name;
// exports = {name, getScore}
})(modules, modules.exports, require);
// require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
// 然后把文件读出来,以 js 的形式解析。
时代的车轮滚滚向前~~~
函数 => 命名空间 => 闭包 => 解决依赖 => cjs 模块化规范
可以看出,技术的发展和迭代都是为了解决人们的一些痛点,然后不断进行创新迭代优化。就如最近大火的 ChartGPT, 不也是为了解放劳动力,提高生产力。让资源更集中,查询更便捷,想法落地更快速。
当然,也引发了一些其他的问题~~~ 毕竟,未知的总是让人焦虑。
貌似有点跑偏,让我们接着奏乐接着舞(学模块化)!!!
什么是模块化规范
模块化规范是为了解决传统用 script 标签引入 js 文件的一些痛点~~~
比如:
<!-- 传统引入js模块的方法 -->
<script src="./A.js"></script>
<script src="./B.js"></script>
一,没有依赖关系
- A 和 B 两个 js 我想 B 先执行完再执行 A
- 我要拿到 B 返回的数据,放到 A 里去执行
二,不能按需解析
- 我要用到 A 时,A 才会被解析(函数调用,接口请求)
基于以上两点,再实际开发和维护过程中,就比较痛苦
所以模块化规范就诞生啦!!!
utils 工具库想必有开发经验的小伙伴肯定不陌生,这算是最常见的模块化。
每个项目大家都约定熟成的封装一些公共的工具函数在 utils 里,方便在各个页面使用,提高开发效率。俗称造轮子。
utils 在 vue 项目中一般使用 ESM 规范导入,为了更好的进行模块之间的依赖,和按需解析。下文中引入的是一个文件,如果 utils 文件夹下面有多个文件,彼此之间通过 ESM 进行依赖,做一些逻辑处理,效果会更明显点。
// 引用方式: esm规范
import { isURL } from "@/utils";
console.log(isURL("https://juejin.cn"));
// 引用方式: cjs规范
let { timestampToHMS } = require("../../utils/index.js");
console.log(timestampToHMS(211312));
模块化规范有哪些
AMD, CMD, CJS(common js), ESM(es 模块), UMD,一般共五种模块化规范
AMD 是一个已经过时的规范,和 CMD 一样都是异步的
一,AMD 模块化规范, RequireJS
define("a", function () {
console.log("a load");
return {
run: function () {
console.log("a run");
},
};
});
define("b", function () {
console.log("b load");
return {
run: function () {
console.log("b run");
},
};
});
require(["a", "b"], function (a, b) {
console.log("main run"); // 🔥
a.run();
b.run();
});
// 依赖前置,引用的时候就执行方法
// a load
// b load
// main run
// a run
// b run
二,CMD 模块化规范, sea.js
// CMD sea.js
define("a", function (require, exports, module) {
console.log("a load");
exports.run = function () {
console.log("a run");
};
});
define("b", function (require, exports, module) {
console.log("b load");
exports.run = function () {
console.log("b run");
};
});
define("main", function (require, exports, module) {
console.log("main run");
var a = require("a");
a.run();
var b = require("b");
b.run();
});
seajs.use("main");
// 依赖后置
// main run
// a load
// a run
// b load
// b run
三,CJS 模块化规范,主要用于服务端编程, nodejs 最早实现了它,是同步的
(function (modules, exports, require) {
// require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
// 然后把文件读出来,以 js 的形式解析。
const $ = require("../juery.min.js");
var name = "张三";
function getName() {}
modules.exports = { name, getScore };
exports.name = name;
// exports = {name, getScore} 不能这样用,会使引用断了
})(modules, modules.exports, require);
四,ESM 模块化规范,现代浏览器规范
import XXX from 'xxx';
export const xxxx;
export default xxxxxxx;
<!--
或者
浏览器中实现了它,ES6规范是浏览器的JS引擎识别JS代码的一种规则
-->
<script type="module">
import XXX from "xxx";
</script>
五,UMD 模块化规范, 通用模块定义,配置了多个模块,兼容 AMD CMD CJS script 浏览器标签
延伸一点点
- 当一个 UMD 模块在 ESM 环境中使用时,可以把它当作普通的 JS 库来使用,通过 script 标签引入即可。
- 而当一个 ESM 模块在 UMD 环境中使用时,则需要使用一些兼容策略,例如将 ESM 模块编译成 UMD 规范,并将其作为 JS 库来使用。
模块化和工程化的关系
提到工程化,不得不提 webpack 和 rollup。
他们两个都是为了把项目中的高阶语法(ES6 写法)打包成浏览器可以识别的低级语法(IIFE),也就是小伙伴们 vue 项目中 dist 目录下的文件。
上面的五种模块化规范,webpack 和 rollup 都可以进行配置打包。
下面是小白会遇到的三个疑问(不要问我为什么知道)
一,打包后,通过配置生成的这五种规范,浏览器可以解析吗?如果不可以,为什么 webpack/rollup 中可以这样配置?
ESM 规范是浏览器的亲儿子,也是我们上文一直提到的。ESM 规范是可以被浏览器解析的。
- 可以直接在 html 文件中 script type=“module"使用 improt export
- 也可以直接在服务器运行的浏览器环境中直接使用 es6 语法,但是不同的浏览器对模块化规范的支持程度有所不同。
那其他几种规范存在的意义是什么呢
- CJS 是给服务器使用的,主要用于服务端编程,因为同步意味着阻塞可能。如果非要在浏览器中解析,也可以通过 Browserify 来把 CJS 的代码转成普通 js(不带 require)
- AMD 和 CMD 是为了解决浏览器端异步加载的问题,需要通过模块加载器来让浏览器解析。AMD(requireJS), CMD(seajs)
- UMD 综合了 CJS 和 AMD 规范的优点,兼容各种运行环境,可以在浏览器和 Node.js 环境中运行。UMD 规范支持多种模块加载器,可以满足不同项目的需求,比较灵活。
二,开发过程中,webpack 可以解析这五种规范吗?
- 当然可以,项目中不论是 import(ESM),require(CJS)等写法,webpack 都可以解析,并且可以打包成你指定的模块化规范。
三,webpack 和 rollup 有啥区别呢?
- Rollup 是提供一个充分利用 ESM 各项特性的高效打包器,更加专注于打包 JavaScript 库和组件。主要优势在于可以生成非常小的包,因为它采用 ES6 模块语法并利用 Tree shaking 技术进行优化。这使得与其生成的包一起使用的代码量最小化。
- Webpack 的设计目标是支持复杂的应用程序打包。它可以将许多不同类型的代码(包括 JavaScript、CSS、图片等)打包为一个或多个文件,同时还支持功能强大的开发工作流,如热替换、代码分割和动态导入。