JS模块化的发展历程,CommonJS、AMD、CMD、UMD、ES6模块化

本文详细介绍了JavaScript模块化的历程,包括CommonJS、AMD、CMD、UMD和ES6模块化。从早期的命名空间、匿名闭包到CommonJS在Node.js中的应用,再到AMD(RequireJS)的异步加载,CMD(SeaJS)的就近依赖,以及UMD的通用定义。最后,文章探讨了ES6的模块化特性,如import和export关键字,以及它们与CommonJS的区别,如静态输出接口和值的引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你应该听过 CommonJS、AMD、CMD、UMD、JS 模块化等等,这篇文章讲的就是这些名称都是什么意思,为什么会出现JS模块化,以及他们分别是如何实现JS模块化的。

最早,我们就是直接在 script 标签里写 JS 代码的

<script>
function add(a, b) {
    console.log(a + b)
}
add(1, 2)
</script>

这时候就有几个问题,一个是代码复用,一个是全局作用域污染,还有就是可维护性问题。

随着代码的增加,这些问题越来越严重。所以首先出现了命名空间和匿名闭包 IIFE 模式,对代码进行封装,并通过提供外部方法来对它们进行访问。

var namespace = {}
namespace.add = function(a, b) {
    console.log(a + b)
}
namespace.add(1, 2)
// IIEF
var utils = (function() {
    var module = {}
    module.multiply = function(a, b) {
        console.log(a * b)
    }
    return module
}())
utils.multiply(1,2)

大多数流行的 JS 库,都使用这种模式,比如 jQuery,所有的函数都在一个全局对象 $ 中。然而,这还是至少需要一个全局变量,而且开发人员需要知道正确的依赖顺序,比如需要控制在 jQuery 加载完成后才能运行带有 $ 的代码。

<script src='./jQurey.js'></script>
<script src='./main.js'></script>

CommonJS

09年 CommonJS(或者称作 CJS)规范推出,在 NodeJS 中实现。主要方法是 exports 和 require。

// utils.js 文件
function add(a, b) {
    console.log(a + b)
}
module.exports.add = add
// main.js 文件
var add = require('./utils').add
add(1, 2)

CJS 出来以后,服务端的模块概念已经形成,很自然地,大家就想要客户端模块。但是 CJS 是同步的,服务端读取本地硬盘可以很快同步加载完成,但是浏览器同步读取服务器端的模块可能需要很长的时间,浏览器将会处于”假死”状态。所以出现异步加载 js 文件的 AMD。

AMD

AMD 是异步模块定义(Asynchronous Module Definition)。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

//utils.js
define([], function() {
    return {
        add: function(a, b) {
            console.log(a + b)
        }
    }
})
// main.js 文件
require(['./utils'], function(utils) {
    utils.add(1, 2)
})

AMD 依赖 RequireJS,例如多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载,就是RequireJS解决的问题。引用模块的时候,将模块名放在数组中作为 reqiure() 的第一参数。如果定义的模块本身也依赖其他模块,同样将依赖模块放在 define() 的第一参数的数组中。

CMD

CMD(Common Module Definition)是玉伯在开发 SeaJS的时候提出来的,SeaJS 要解决的问题和 RequireJS 一样。不同于 AMD 的依赖前置,CMD 是就近依赖。

// AMD
require(['./utils', 'a', 'b'], function(utils) {
    console.log(1)
    // 还没有用到 utils a b 等模块,但是 AMD 已经初始化了所有模块
    console.log(2)
    utils.add(1, 2)
})
//CMD
define(function(require, exports, module){
    console.log(1)
    if(false) {
        var utils = require('./utils') // 需要时再 require,不执行就不会加载
        utils.add(1, 2)
    }
})

但是在 AMD 也是支持依赖就近,也就是 CMD 这样的写法的,所以,RequireJS 中,以上两种方式都能执行。不过,RequireJS 官方文档中,默认都是采用依赖前置的写法。

UMD

再说 UMD,通用模块定义(Universal Module Definition),比如你写了一段代码或者写了一个库,在服务器端和浏览器端都会用到,难道要维护 CJS 和 AMD 两套代码吗,这时候,UMD 就来了。它其实就是帮你判断应该用 AMD 还是 commonJS,是哪个就用哪个方式来定义模块,都不是的话就挂到全局对象上。

// utils.js 文件同上
(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        //AMD
        define(['utils'], factory)
    } else if (typeof exports === 'object') {
        //CommonJS
        var utils = require('utils')
        module.exports = factory(utils)
    } else {
        root.result = factory(root.utils)
    }
}(this, function(utils) {
    utils.add(1, 2)
}))

ES6

最后 ES6(ES2015)自带的模块化,这个大家应该就比较熟悉了,使用 import 和 export 关键字来导入和导出模块。不熟悉 ES6 的赶快去阮老师那里补课。

export const utils = {
    add: function(a, b) {
        console.log(a + b)
    }
}
// main.js 文件
import { utils } from "./utils"
utils.add(1, 2)

这里再说一下 CommonJS 和 ES6 的区别,第一个

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

CommonJS 模块输出的是一个值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值。比如

// utils.js 文件
var count = 0
function add(a, b) {
    console.log(a + b)
    count++
}
module.exports = {add, count}
// main.js 文件
var utils = require('./utils')
utils.add(1, 2)
console.log(utils.count) // 0

虽然执行 add 函数使模块中的 count++ 但是引入 utils.count 时就是 0,模块内部改变并不会导致引入的值的变化。

ES6 模块输出的是值的引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。到上面代码最后一行时候才会去模块中读取 utils.count 的值,这时候的输出就是 1 了,大家可以试一下。

第二个区别是

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

尾声

我们在开发过程中可能对模块化如何实现的关注比较少,因为各种工具都可以帮我们转换成各种需要的模块化方式,比如 TS 可以生成各种模块加载系统使用的代码。不过,看完这篇文章有没有搞清楚 JS 模块化呢。

参考 https://juejin.im/post/5b5069f56fb9a04fb136de33 https://www.html.cn/archives/7628 https://es6.ruanyifeng.com/#docs/module-loader https://juejin.im/post/5e3985396fb9a07cde64c489 https://typescript.bootcss.com/modules.html https://juejin.im/post/5c17ad756fb9a049ff4e0a62

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值