浅谈前端模块化

刚工作那会,因为对开发岗位知识体系认知不足,所以短期内迅速学习补充了大量的资料,一切的目的都是为了让这些知识能支撑我日常的工作,避免写不出代码、亦或是避免别人说到了相应的知识点自己茫茫然的尴尬。

这样速成的缺点就是,自己有些知其然而不知其所以然。

随着时间的推移及对技术上学习的深入,才发现脑海中学过的知识框架逐渐具像化,变得越来越清晰,也真正的形成了学习上的一个闭环。

那么今天就来谈谈最开始学习过的前端模块化。

浅谈前端模块化

在讲具体定义之前,先来说一个场景,当你在写代码的时候,比如写一个函数,这个函数内部需要一个方法来判空,这时你想:要将这个判空方法提取到外面封装一下,避免写在业务函数里,所以你将这个判空方法提取到同文件的页面,并完成了你的需求。好,这证明你有很好的解耦思想。

但是,随着你项目的扩大,你会发现,这个判空方法你不仅仅在当前页面需要,在其他页面也需要,这时,你会想着将这个方法提取到一个公共的文件里,然后在需要判空方法的页面都能调用这样就能避免少写一些代码。好,这证明你有很好的模块化思想(这很重要)。而当你对这个方法抽离,在别的文件内引入使用的过程中,其实就是一个模块化的过程。

什么是模块化

模块化是一种将复杂系统分解为可管理、可维护、可复用的代码单元的方法。

它有助于提高代码的可读性、可维护性和可复用性。

模块化使开发者能够构建更大、更复杂的应用程序,同时保持代码的可管理性和可读性。

简单来说就是 解耦、方便阅读、提高可维护性。

模块化标准历程

CommonJS

CommonJS 是在2009年由JavaScript社区提出的。

CommonJS 是一个用于服务器端 JavaScript 模块化的规范。最初的目标是为服务器端 JavaScript 提供一个模块系统,但随着 Node.js 的流行,CommonJS 也成为 Node.js 模块系统的基础。CommonJS 规范定义了模块的格式和如何加载模块,从而使开发者可以将代码拆分成独立的模块,便于管理和复用。

根据 CommonJS 规范,一个单独的文件就是一个模块,每一个模块都是一个单独的作用域,对其他文件是不可见的。

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。

CommonJS 模块在首次加载后会被缓存。后续对同一模块的加载将返回缓存中的模块,而不会再次执行模块代码。

CommonJS 导出导入方式

CommonJS 使用 requiremodule.exports 来进行导入导出。

1、module 对象

在CommonJS中,每个文件都是一个模块,module对象代表当前模块。module对象有一个 exports 属性,用于导出模块的公开接口。

1
2
3
4
5
6
7
8
9
10
//util.js

module.exports = {
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
}
};
2、exports 对象

exports是 module.exports的一个引用。默认情况下,exports和module.exports指向同一个对象,因此可以使用exports来添加导出的属性和方法。

1
2
3
4
5
6
7
8
9
10
//util.js

exports.add = function (a, b) {
return a + b;
};

exports.subtract = function (a, b) {
return a - b;
};

3、require 函数

require 函数用于加载模块。返回exports对象

1
2
3
4
//index.js
const math = require('./util');
console.log(math.add(2, 3)); // 5
console.log(math.subtract(5, 2)); // 3

AMD (Asynchronous Module Definition)

AMD是在2010年由RequireJS提出的。

AMD规范全称是Asynchronous Module Definition,即异步模块加载机制,主要用于浏览器。它完整描述了模块的定义,依赖关系,引用关系以及加载机制。由于该规范不是原生js支持的,使用AMD规范进行开发的时候需要引入第三方的库函数,AMD对应的就是鼎鼎大名的RequireJS。

CommonJS规范出现后,在Node开发中产生了非常好的效果,开发者希望借鉴这个经验来解决浏览器JS的模块化
但是大部分人认为浏览器和服务器的环境差别太大,毕竟浏览器JS是通过网络动态以此加载的,而服务器的JS是保存在本地磁盘中。因此浏览器需要实现异步加载,模块在定义的时候就必须先知名它所需要依赖的模块,然后把本模块的代码写在回调函数中执行,最终衍生出了AMD规范
AMD的主要思想时异步模块,主逻辑在函数回调中执行。

AMD 导出导入方式

AMD使用 requiredefine 来导入和导出。

1、define 函数

define 是 AMD 规范中用来定义模块的方法。它接受三个参数:模块 ID(可选)、依赖数组和函数。函数在所有依赖模块加载完成后执行,并返回模块的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

define('util', ['dependency1', 'dependency2'], function (dep1, dep2) {
// 模块代码
return {
// 模块接口
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
}
};
});

上述代码是我们定义暴露了一个util模块,这个模块依赖于dependency1,和dependency2, 当加载完依赖模块后会执行函数体内容。

2、require 函数

require 是 AMD 规范中用来加载模块的方法。它接受两个参数:依赖数组和回调函数。回调函数在所有依赖模块加载完成后执行。

1
2
3
4
5
6
require(['util'], function (util) {
// 使用模块
console.log(util.add(2, 3)); // 5
console.log(util.subtract(5, 2)); // 3
});

上述代码是在其他页面引入定义好的util模块并使用。

UMD

UMD(Universal Module Definition)是一种用于兼容多种 JavaScript 模块化规范的模式。它能够在同一个模块中同时支持 AMD、CommonJS 和全局变量三种加载方式,使模块能够在浏览器、Node.js 和其他环境中都能正常运行。

UMD 模块化的核心思想是检测当前的环境,并根据环境采用不同的模块定义方式。这使得同一个模块能够在多种环境中运行,而无需为不同环境编写不同的代码。

ES6

ES6(ECMAScript 2015)模块化是 JavaScript 语言标准中引入的一种新的模块化机制。与之前的模块化规范(如 CommonJS 和 AMD)相比,ES6 模块化在语法和功能上提供了更强大的支持,并且是 JavaScript 原生支持的模块系统。

ES6 模块化具有静态结构,这意味着模块的依赖关系在编译时就能确定,而不是在运行时。这有助于提高工具和编译器的性能,并且可以实现更高级的代码分析和优化。

ES6模块化中每个模块只会被加载一次,并且模块中的状态在整个应用程序中是共享的。这意味着如果多次导入同一个模块,实际上引用的是同一个模块实例。

ES6 导出导入方式

ES6 使用 exportimport 来导入和导出。

1、导出(Export)

通过 export 关键字,可以将模块中的变量、函数、类等导出,使它们能够被其他模块导入和使用。

命名导出允许在一个模块中导出多个内容:

1
2
3
4
5
6
7
8
9
10
11
12
// module.js 
export const name = 'VapausQi';
export function myFunction() {
console.log('Hello, world!');
}
export class MyClass {
constructor() {
console.log('MyClass instance created');
}
}


默认导出允许在一个模块中只导出一个内容,:

1
2
3
4
// module.js
export default function() {
console.log('This is the default export');
}
2、导入(Import)

通过 import 关键字,可以在其他模块中引入导出的变量、函数、类等。

导入命名导出:

1
2
3
4
5
6
7
// main.js
import { name, myFunction, MyClass } from './module.js';

console.log(myVariable);
myFunction();
const instance = new MyClass();

导入默认导出:

1
2
3
4
// main.js
import myDefaultFunction from './module.js';

myDefaultFunction();

别名导出:

1
2
3
4
5
// main.js
import { myVariable as renamedVariable } from './module.js';

console.log(renamedVariable);

导入整个模块: 使用星号(*)可以导入整个模块,并赋予它一个新的命名:

1
2
3
4
5
6
7
// main.js
import * as myModule from './module.js';

console.log(myModule.name);
myModule.myFunction();
const instance = new myModule.MyClass();

总结

以上就是前端模块化的几种方式,包括CommonJS、AMD、UMD和ES6模块化。每种模块化方式都有其特点和适用场景,开发者可以根据具体需求选择合适的模块化方式。

其实到了这里,我们回头看我们现在很多常用的工具库,就比如前面博客讲到的Fuse.js开源模糊搜索工具,Fuse这个工具库,为什么它可以支撑多种引入方式,无非就是通过同一个入口文件,通过打包工具打包出多个兼容不同模块化的文件,供外部引用使用(后续有时间会出文章详细讲解一下这里)。

OK,今天的文章就到这里啦,如有不正确的地方欢迎指出,会及时更正。