Skip to content

Latest commit

 

History

History
272 lines (170 loc) · 9.86 KB

044. 模块化.md

File metadata and controls

272 lines (170 loc) · 9.86 KB

一、前言

模块化是一种软件设计的概念,用于将大型软件系统分解为更小、可重用的模块或组件。

模块化的好处包括:

  • 代码隔离和封装:模块化可以将代码分割为独立的模块,每个模块具有自己的作用域,避免了命名冲突和全局变量污染,增加了代码的可靠性和可维护性。
  • 代码复用:模块化使得可以将可重用的代码抽象为模块,并在需要时进行引用和复用,减少了重复编写代码的工作量。
  • 团队协作:模块化可以促进团队的协作开发,每个成员可以独立开发和维护各自的模块,降低了代码冲突和合并的风险。
  • 性能优化:模块化可以实现按需加载,只加载所需的模块,减少了不必要的资源消耗,提高了应用程序的加载速度和性能。

由于一开始 JavaScript 本身没有提供模块化的机制,所以才会衍生出 CommonJS、AMD、ES Modules(ESM)和UMD这么多模块化规范。JavaScript 在 ES6 时原生提供了 importexport 模块化机制( ESModule )。

二、发展历程

思维导图 >>

1. 混沌时代(IIFE - 自调用函数)

早期并无 模块化 的概念,我们将所有的 js 文件放在一块,代码执行顺序就按照文件顺序执行。

<script src="./libs/jquery-3.6.0.min.js"></script>
<script src="./js/helper.js"></script>
<script src="./js/render.js"></script>
<script src="./js/index.js"></script>

这样做的缺点就是 污染全局作用域,每一个模块都是暴露在全局中的,容易产生 命名冲突,还要 手动处理各代码的依赖关系

因此,我们使用自调用函数来编写模块化,其特点就是:在一个单独的函数作用域中执行代码,避免变量冲突。

(function () {
  // 模块代码
})();

温馨提示:当然,在早期时候,一个函数 或者 一个对象也可以理解为一个模块 。

2. commonJS

CommonJS 是 Node.js 最早采用的模块化规范。它于 2009 年发布,并在 Node.js 社区得到广泛应用。CommonJS 使用 require() 导入模块,使用 module.exportsexports 导出模块。

在CommonJS中,一个文件就是一个模块,内部定义的变量就属于这个模块里的,不会对外暴露,所以不会污染全局变量。

CommonJS 同步加载 模块,等当前模块加载完成了才进行下一步,服务器端文件都是保存在硬盘上,所以同步加载没问题。但是浏览器上,需要把文件从服务器端请求过来,比较慢,所以同步加载不适合用在浏览器上。

@API

  • 导出:module.exports = {} | exports.xxx = 'xxx'
  • 导入:require("path")

@Examples

module.exports = {
  foo: 'Hello',
  bar: 30,
};

exports.foo = 'Hello';
exports.bar = 30;

const moduleA = require('./moduleA');
console.log(moduleA); // { foo: 'Hello', bar: 30 }

提示:

  1. exports 是对 module.exports 的引用,不能直接给 exports 赋值,直接赋值无效,结果是一个空对象,module.exports 可以直接赋值。
  2. 一个文件不能写多个 module.exports ,如果写多个,对外暴露的接口是最后一个 module.exports
  3. 模块如果没有指定使用 module.exports 或者 exports 对外暴露接口时,在其他文件就引用该模块,得到的是一个空对象。

3. AMD

由于 commonJS 规范不适用于浏览器,因为要从服务器加载文件,不能用同步模式,所以有了AMD(Asynchronous Module Definition,异步模块定义)规范。AMD是一种异步模块定义规范,于 2011 年由 Dojo 社区提出。它主要用于浏览器环境,支持动态加载模块。AMD 使用 define() 来定义模块,使用 require() 来异步加载模块。

依赖前置,require([dep1, dep2], callback),先加载依赖再执行回调函数。

优点是可以在浏览器环境中 异步加载 模块,而且可以 并行加载 多个模块。

目前,主要有两个Javascript 库实现了AMD规范:

@API

  • 定义模块:
    • 有依赖:define(['module1', 'module2'], (m1, m2) => { return 模块; })
    • 无依赖:define(() => { return 模块 })
  • 引入:require(['module1', 'module2'], (m1, m2) => { 使用m1, m2模块 })

@示例

// A.js
define(function () {
  const splitBirth = (idCard) => {
    if (isNaN(idCard) || idCard.length !== 18) {
      return null;
    }
    return {
      year: idCard.slice(6, 10),
      month: idCard.slice(10, 12),
      day: idCard.slice(12, 14),
    };
  };
  return {
    splitBirth,
  };
});
// B.js
define(["./A.js"], function (A) {
  const getBirth = (idCard) => {
    const obj = A.splitBirth(idCard);
    if(obj) {
      return `出生年月:${obj.year}${obj.month}${obj.day}日`;
    }
    return "";
  };
  return {
    getBirth
  }
});
// main.js
require(["./utils/B.js"], (B) => {
  const birth = B.getBirth("510321199307161234");
  console.log(birth); // 出生年月:1993年07月16日
});

4. CMD

CMD(Common Module Definition)是一种用于在浏览器环境下实现模块化的规范,与 AMD 类似,但有些细微的差异.

  1. 定义模块: 使用 define 函数定义模块。define 函数接受一个工厂函数作为参数,工厂函数中可以通过 require 函数引入依赖的模块,并通过 module.exports 导出模块的内容。例如:

    define(function(require, exports, module) {
      // 引入依赖模块
      var dependency1 = require('dependency1');
      var dependency2 = require('dependency2');
    
      // 导出模块内容
      exports.foo = 'Hello';
      exports.bar = 30;
    });
  2. 导入模块: 使用 require 函数导入模块。require 函数接受一个模块名称作为参数,并返回该模块的导出内容。例如:

    var moduleA = require('moduleA');
    var moduleB = require('moduleB');
    // 使用导入的模块
  3. 加载器配置: 在使用 CMD 之前,需要配置相应的加载器(如 Sea.js)来处理模块的加载和依赖关系。加载器通常需要在页面中引入,并通过配置选项指定模块的路径和别名等信息。

CMD 的使用方式与 AMD 类似,都是通过异步加载模块来实现模块化。CMD 更加强调 按需加载延迟执行,它的模块定义和导入方式相对更加简洁和直观。CMD 在国内的前端开发中比较常见,特别是在大型项目和模块化开发中有一定的使用率。

5. UMD

严格上说,UMD(Universal Module Definition) 不能算是一种模块规范,因为它没有模块定义和调用,CommonJS、AMD、CMD并行的状态下,就需要一种方案能够兼容他们,这样我们在开发时,就不需要再去考虑依赖模块所遵循的规范了,而UMD的出现就是为了解决这个问题。

6. ES Modules(ESM)

ES Modules 是 ECMAScript(JavaScript)的官方模块化规范,自 ES6(ES2015)开始引入。它于 2015 年发布,并逐渐成为主流的模块化规范。ES Modules 使用 import 导入模块,使用 export 导出模块,可以在浏览器和 Node.js 环境中使用。

机制:ES6的模块机制在依赖模块时并不会先去预加载整个脚本,而是生成一个只读引用,并且静态解析依赖,等到执行代码时,再去依赖里取出实际需要的模块。

特点:编译时加载,不允许在里边引用变量,必须为真实的文件路径。可以通过调用 import() 语句,会生成一个promise去加载对应的文件,这样子就是运行时加载,可以在路径里边编写变量

缺点:浏览器暂不支持,需要babel编译过

@API

@示例

导出单个属性

export const a = 10;
export const b = 20;

import { a, b } from './module.js';

导出属性列表

const a = 10;
const b = 20;
export { a, b };

import { a, b } from './module.js';

重命名导出

export { a as x, b as y };

import { x, y } from './module.js';

默认导出

export default { foo: 'bar' };

import module from './module.js';
console.log(module.foo);

导入所有

import * as xx from './module.js';

三、总结

# IIFE CommonJS AMD CMD ES6
实现 浏览器端 服务端 浏览器端 浏览器端 浏览器端
加载方式 顺序加载 同步加载(运行时) 异步加载(依赖前置) 按需加载/延迟加载 编译时
/ / requirejs >> seajs >> /
来源 前端社区 前端社区 前端社区 前端社区 官方
是否编译