JavaScript UMD 规范源码推演与模块化解析
本文详细推演了 JavaScript UMD(Universal Module Definition)规范的源码结构,解析其如何同时兼容 AMD 和 CommonJS 模块系统。通过逐步拆解自执行函数、工厂模式及环境检测逻辑,揭示 UMD 作为过渡期模块化标准的核心机制,并提供适用于 jQuery 插件等场景的具体代码范式。
UMD 规范概述
1. UMD 规范
地址:https://github.com/umdjs/umd
UMD 规范被戏称为所有模块规范中实现最复杂的一个,其核心目的是为了让模块能够同时兼容 AMD 和 CommonJS 两种规范。它主要被那些需要同时支持浏览器端和服务端引用的第三方库所采用。UMD 是特定历史阶段的产物,随着各种运行环境最终统一采用 ES Harmony 标准,UMD 也将逐步退出历史舞台。
UMD 规范的结构乍看之下非常复杂,主要原因在于理解这段范式需要具备一定的 JavaScript 基础知识。其基本结构如下所示:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'), require('underscore'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 方法
function a(){}; // 私有方法,因为它没被返回 (见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
}));
源码范式推演
2. 源码范式推演 2.1 基本结构
首先观察最外层的代码结构:
(function (){}());
这非常简单,就是一个自执行函数。既然它被定义为一种模块化标准,意味着这个自执行函数最终需要导出一个模块。从代码实现的角度来看,通常有两种常见的实现方式:
- 通过 return 语句返回一个模块对象;
- 将实参传入一个对象,把函数内部生成好的需要导出的内容挂载到这个对象的属性上;
观察上述函数体内部并没有 return 语句,因此可以推测 UMD 在实现时采用了第二种方式。既然 UMD 是一种模块化规范,其功能就是根据使用要求生产模块,因此它的职责定位可以称为“模块工厂”。我们可以定义一个 factory 方法,每当执行该方法时,就返回一个模块。因此,其基本结构演变为如下形式:
(function (factory){
//假设没有使用任何模块化方案,那么将工厂函数执行后返回的内容直接挂载到全局
window.Some_Attr = factory();
}(function(){
//自定义模块主体的内容
/*
var a,b,c
function a1(){}
function b1(){}
function c1(){}
return {
a:a1,
b:b1
}
*/
}))
也就是说,我们自定义一个匿名函数,然后把它作为实参传递给自执行函数。在自执行函数内部,通过形参来访问这个工厂方法(或者你更熟悉回调函数 callback 这样的叫法),并将其简单地挂载到全局对象上,从而完成基本的模块导出。
有时我们也希望将模块挂载到非全局的环境中,将挂载对象动态传入可以让代码变得更加灵活。这里涉及一个基础知识:浏览器环境中的全局对象拥有 parent、top、self 三个属性来追踪页面中嵌入 <iframe> 后引入的新 Window 对象。单页面 Window.self 是指向自己的,代码中常通过是否包含 self 属性来鉴别全局对象。因此,此处的写法可以改进为兼容版本:
(function(root,factory){
root.Some_Attr = factory();
}(self !== undefined ? self : this, function(){
}));
适配 AMD
2.2 适配 AMD
接下来,我们先来加入 AMD 规范的适配。规范地址:AMD 规范 github 地址:https://github.com/amdjs/amdjs-api/blob/master/AMD.md
/*
* AMD规范的模块定义格式是define(id?, dependencies?, factory),factory就是实际的模块内容
*/
(function (factory){
//判断全局环境是否支持AMD标准
if(typeof define === 'function' && define.amd){
//定义一个AMD模块
define([/*denpendencies*/],factory);
}
}(function(/*formal parameters*/){
//自定义模块主体的内容
/*
var a,b,c
function a1(){}
function b1(){}
function c1(){}
return {
a:a1,
b:b1
}
*/
}))
适配 CommonJs
2.3 适配 CommonJs
接着我们先来加入 CommonJs 规范的适配:
/*
* CommonJs规范使用require('moduleName')的格式来引用模块,使用module.exports对象输出模块,所以只要把模块的输出内容挂载到module.exports上就完成了模块定义。
*/
(function (factory){
//判断全局环境是否支持CommonJs标准
if(typeof exports === 'object' && typeof define !== 'function'){
module.exports = factory(/*require(moduleA), require(moduleB)*/);
}
}(function(/*formal parameters*/){
//自定义模块主体的内容
/*
var a,b,c
function a1(){}
function b1(){}
function c1(){}
return {
a:a1,
b:b1
}
*/
}))
加入对 CommonJs 的适配后,函数主体中 return 的内容(一般是一个对象)就被挂载到了 module.exports 上。如果你编写过 node.js 代码,对此一定不会陌生。
把上面的片段揉到一块,你也就看懂 UMD 的样子了。
更具针对性的 UMD 范式
3. 更具针对性的 UMD 范式
UMD 在其 github 主页上提供了更具针对性的范式,适用于不同的场景,感兴趣的读者可以自行查看(地址在第一节已经给出)。

在此贴一个可能对大多数开发者比较有用的 jqueryPlugin 的开发范式,如果看懂了上面的分析,那么下面的代码应该不难看懂:
// Uses CommonJS, AMD or browser globals to create a jQuery plugin.
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = function( root, jQuery ) {
if ( jQuery === undefined ) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if ( typeof window !== 'undefined' ) {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
$.fn.jqueryPlugin = function () { return true; };
}));
模块化开发
4. 模块化开发
前端模块化本身是一个稍显混乱的话题,笔者自己最初也是 require( ) 和 require.js 傻傻分不清楚,但模块化是前端开发中非常重要的课题。如果你不想一辈子只是在一个页面内写代码,这一关是一定要过的,感兴趣的读者可以按照下面的基本类别划分分块进行学习。

JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
