一文带你了解Javascript模块化(CommonJs、ADM、UMD、CMD、ESM)

你知道JS模块化吗?你对模块化是怎么理解的呢?为什么需要模块化?开发中如何构建模块化?require,import 的区别和关系是什么?commonjs,amd,cmd 又是什么呢?如果你概念不是很清楚,那就一起来学习下吧!

模块化:

百度百科定义:是指解决 一个复杂问题自顶向下 【逐层把系统划分】成若干模块的过程,有多种属性,分别反映其内部特性。

对模块化的理解:

简单来说就是---为了更好的管理,把复杂的系统分解。

再说的具体点就是----实现一个功能的一系列的【相关状态数据方法等】在一个单独的文件里

为什么需要模块化?

最开始的时候,js是没有模块化的,就是我们知道的<script></script>标签,但随着网络的发达,用户需求的增长,前端需要做的事情越来越多,程序也随之越来越复杂。复杂随之而来的就是管理复杂。所以为了更好地管理复杂的系统,也为了更好的复用,模块化也就变得越来越重要了。

模块化优点:

  • 更好维护
  • 复用性更好
  • 更好扩展
  • 管理依赖

模块化的三个阶段:

阶段一:基于文件划分

这个时期,约定一个文件就是一个模块

缺点:

  • 命名容易冲突,不利于多人开发
  • 污染全局作用域
  • 无法管理模块与模块之间的依赖关系
  1. index.html中 利用script 引入模块化js文件
html 复制代码
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>模块化</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
  // 一个script就对应一个模块
  <script src="./index.js"></script>
  <script>
     // 调用模块中的变量,容易造成命名冲突,变量污染
     console.log("当前时间", currentTime)
  </script>
</html>
  1. 提取相关功能到独立的index.js文件中
js 复制代码
const currentTime = new Date().getTime();
const getDate = ()=>{
    
}

阶段二:命名空间方式

约定每个模块只暴露一个全局的对象,把一系列方法属性,都挂在一个对象上。

缺点:

  • 依然没有私有空间,外部可以随意修改内部的方法和属性
  • 无法管理模块间依赖关系
js 复制代码
const module = {
    time: 1,
    getTime: function(){
        return new Date().getTime()
    }
}

阶段三:IIFE 立即执行函数

由于函数拥有独立的作用域,所以可以通过闭包来实现 【模块私有作用域 】,对于外部需要的可挂载在window上

优点:

  • 拥有独立的作用域
  • 不会污染全局作用域

应用:

  • jquery框架 就用的这种方式
js 复制代码
(function() {
    // 拥有独立作用域
})()
js 复制代码
// IIFE module
const module = ((agr1,agr2) => {
    let count = agr1;
    return {
        increase: () => {
            count ++;
            console.log(count);
        },
        init:  () => {
            count = 0;
            console.log(count);
        }
    }
})(5,6);
// 调用
module.increase();

以上三个阶段是早期没有工具和规范的情况下的方案,所以后期出现了比较成熟的模块化加载方案。

模块化规范 :

现在有四种比较成熟的模块加载方案:

👍🏻 值拷贝:一个值一旦输出,模块内部的改变就影响不到这个值了

1. commonJS

  • 引入方式:require
  • 输出方式: exports.xx
    • commonjs2 :多了 module.exports = {xx1,}
  • 拷贝方式:值拷贝
  • 加载方式:同步
  • 加载时机:运行时加载
  • 应用:node.js使用的就是commonjs 规范
1.1 require原理:

💻 require原理:

  1. 是通过动态创建script脚本异步引入模块的,
  2. 然后对每个脚本的load事件进行监听
  3. 如果每个脚本都加载完成了, 再调用回调函数

require的核心概念:

  • 导出 的文件中:定义module.exports,导出的对象类型不予限定(可为任意类型)。
  • 导入 的文件中:使用require()引入即可使用。

本质上,是将要导出的对象,赋值给module这个对象的exports属性,在其他文件中通过require这个方法来访问exports这个属性。 如:require(./a.js) = exports

1.2 为什么commonjs 规范不适合浏览器端?

commonjs规范多用于服务端, 因为 nodejs 这种主要用于服务器的编程,加载的模块文件一般都已经存在在本地硬盘 ,所以加载起来比较快 ,所以同步就可以了,不用考虑异步加载方式。

而浏览器主要是通过网络请求等方式,所以使用异步更合适。所以出现了更适合的AMD、CMD异步规范。👇🏻

2. AMD方案

  • 依赖前置:声明的时候就依赖,2.0后也可以延迟执行
  • 加载方式:异步
  • 应用: require.js
js 复制代码
// 定义:依赖需要提前写好
define('myModule', ['moduleA', 'moduleB'], (moduleA, moduleB) => {
    // 使用moduleA.xxx
    // 使用moduleB.xxx
})
// 使用
require(['myModule'], (myModule) => {
    // 使用myModule
})
js 复制代码
// ===AMD兼容已有代码=======》
define('module', [], () => {
  var moduleA = require(./moduleA);
  var moduleB = require(./moduleB);
  const func1() {
    moduleA.xxx;
  }
  const func2() {
    ttt;
    moduleB.xxx;
    moduleA.xxx;
  }
  return { func1, func2 };
})
// ===End==============
2.1 UMD规范
  • umd 是AMD和commonjs 规范的糅合
  • 可以自己写判断,nodejs环境使用commonjs规范,浏览器使用amd规范

3. CMD方案

  • 加载方式:异步
  • 应用:sea.js
  • 特点:按需加载,就近原则
  • 缺点:
    • 依赖打包,打包的时候才去判断是否需要按需加载
    • 加载逻辑存在所有模块,打包体积变大
js 复制代码
define('module',(require, exports, module)=>{
   let a = require(a);
   let b = require(b);
})

4. ES6方案提出的方案ESM规范: import 和export

  • 编译时确定依赖关系,静态加载(commonjs和amd 是运行时确定)
  • 自动使用严格模式
  • es6模块本身无法被引用
  • 顶层this指向undefined,所以不应该在顶层代码中使用this
  • 值引用:可以实时取得模块内部的值,commonjs 是值的拷贝,一旦输出,不会改变
import:本质就是是在编译阶段执行的,在代码运行之前
  • import具有提升效果,会提升到整个模块头部先执行(早于require)
  • 静态的:所以不能使用表达式和变量,不允许运行时改变
  • 多次重复只会执行一次
方式一:export

export:可以导出多个,导入时不可以改名字,但是可以as别名

js 复制代码
const name = "清华";
const age = 12;
export {
    name,
    age as aliasAge
}
js 复制代码
import {name,aliasAge} from 'xx.js'
方式二:加载默认模块

export default: 只能导出一个,导入时可以改名字

js 复制代码
const name = "清华";
const age = 12;
export defalut name;
js 复制代码
// 可以改名字
import aliasName from 'xx.js'
方式三:整体加载
js 复制代码
// 可以改名字
import * from 'xx.js'

四种规范表格总结:

1. AMD 和CMD 的区别:
区别 AMD CMD
模块定义时:对依赖的处理 依赖前置:定义的时候就要声明其依赖的模块 就近依赖:用到的时候才去require
依赖模块的执行时机 加载完成后立即执行执行顺序和书写 顺序不一定一致 依赖模块加载完成后不执行, 只下载顺序一致
模块执行 提前执行(RequireJs2.0也可以延迟执行) 延迟执行
主要应用 require.js sea.js
加载方式 异步 异步

CMD 是等所有的依赖都加载完成后,进入回调函数逻辑,遇到require语句的时候才执行对应的模块,这样执行顺序和书写顺序就保持一致了

2. ESM 和CommonJs规范的区别:
对比 ES6 CommonJS
输出值 值引用 值拷贝
加载时机 编译时输出接口 运行时加载
对外接口 一种【静态定义】,在代码静态解析阶段就会生成 是对象:即在输入时先加载整个模块,生成一个对象,然后从这个对象上取方法
输出方式 export 或 export default module.exports

Interview:

  1. 对模块化的理解?

    模块化主要用于管理复杂的系统,将实现一个功能的一系列方法状态等放在一个模块中,前期约定一个文件就是一个模块、挂在对象上、闭包+函数的私有作用域特点实现模块私有作用域等。后面逐渐出现了成熟的模块化规范,如 commojs,amd,cmd,es6等。
    早期前端模块化并没有采取commonjs规范,而是重新设计了规范,AMD 可以说是前端模块化演进道路上的一步,是一种妥协的实现方式,不能算是最终的解决方案。只不过在当时的环境背景下,AMD 还是很有意义的,它毕竟给前端模块化提供了一个标准。除了Requirejs使用的AMD规范,同期还出现了淘宝推出的Sea.js 库,但实现的是另一个标准CMD,类似于Commonjs。但是他们都会有难以接受的问题。
    随着技术的发展,js标准逐渐完善、基本已经是Nodejs环境中使用commonjs,浏览器环境中使用ES6的模块化,也就是import 和 export。
    最早在ESM这个标准刚推出的时候,所有主流的浏览器基本上都不支持这个特性,但是随着 Webpack / Rollup 等一系列的打包工具的流行,这个规范才开始逐渐的普及。截至目前,ES Modules 基本上可以说是最主流的前端模块化方案了,相比于 AMD 这种社区提出来的开发规范,ES Modules 可以说是在JS语言层面实现了模块化,所以说它更为完善。

  2. js 模块化规范有几种?

    比较成熟的有4种,amd,cmd,commonjs,es6的import+export

  3. amd,cmd,commonjs,es6的import+export 区别?

    • commonjs多用于后台语言,是随着nodejs出现的,同步,值拷贝,运行时加载
    • amd是浏览器端的规范,异步,主要是requirejs提出的规范,依赖前置
    • cmd也是浏览器的规范,异步,主要是sea.js 提出的规范,按需加载
    • es6 是值引用,编译时确定依赖
  4. require 和import的区别?

    • require 是commonjs 规范,同步,值拷贝,运行时加载
    • 而import是es6的模块化规范,异步,值引用,编译时加载,import提升,静态,重复引入会执行最后一次

注解:

RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。 他们都推动了模块化的开发的思想。

区别:

区别 require.js sea.js
定位 想成为浏览器\Rhino\Node等环境的模块加速器 专注于web浏览器端
规范 amd cdm
推广理念 在尝试让第三方类库修改自身来支持 RequireJS 自主封装的方式来"海纳百川",目前已有较成熟的封装策略。
开发调试支持 无明显支持 支持debug,nocache
插件机制 源码中预留接口,插件类型单一 通用事件机制,插件类型更丰富
  • RequireJS :是一个JavaScript模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境,就像 Rhino and Node。使用RequireJS加载模块化脚本将提高代码的加载速度和质量。

  • SeaJS :是一个遵循CMD规范 的JavaScript模块加载框架 ,可以实现JavaScript的模块化开发及加载机制。

    SeaJS不会扩展封装语言特性,而只是实现 JavaScript的模块化及按模块加载
    SeaJS的主要目的 是令JavaScript开发模块化并可以轻松愉悦进行加载 ,将前端工程师从繁重的JavaScript文件及对象依赖处理 中解放出来,可以专注于代码本身的逻辑。

    SeaJS可以与jQuery这类 框架完美集成

    使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决 JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题方便代码的编写和维护

    SeaJS的作者是前淘宝UED,现支付宝前端工程师玉伯。

相关推荐
学代码的小前端1 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_856 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
程序猿进阶6 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
还是大剑师兰特28 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_7482361137 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss