前言
最近翻看 underscore v1.13.6 的源码,开头就有一段很有意思的代码,这里对这段代码稍微分析一下,顺便查缺补漏,记录一下。
正文
代码片段
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define('underscore', factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () {
var current = global._;
var exports = global._ = factory();
exports.noConflict = function () { global._ = current; return exports; };
}()));
}(this, (function () {
...
})));
分析过程
首先,这个片段的大概结构是一个立即执行函数:
(function (global, factory)){}(this, factoryFunc))
这里的 this 对应的是形参 global,在浏览器侧对应 window 对象,在 node 环境对应 global 对象;factoryFunc = (function(){}), 是一个工厂函数,对应形参 factory。
然后,这段嵌套的三元表达式判断究竟是什么意思? 简化来看,这个嵌套的三元表达式是这样的:
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = factory();
} else if (typeof define === "function" && define.amd) {
define("underscore", factory);
} else {
(global = typeof globalThis !== "undefined" ? globalThis : global || self),
(function () {
var current = global._;
var exports = (global._ = factory());
exports.noConflict = function () {
global._ = current;
return exports;
};
})();
}
直观来看:
- 先判断
exports是否是个对象,且module不为undefined,那么就将工厂函数以 commonjs 的形式导出; - 再判断是否存在
define方法,存在,则默认当前为浏览器环境,以 amd 形式导出模块; - 最后,那就当作是其他环境情况下,如果
globalThis不为undefined,就给全局属性 global 赋值为globalThis,否则使用 global 以及兜底 self 赋值;接着执行一个立即执行函数,函数体内容主要是重置 global._ 并导出,同时导出了一个noConflict方法,用于让渡变量_的控制权,用于解决命名冲突的问题。补充一句:noConflict 方法在许多库中都有这样类似的实现,例如:jQuery、backbone、lodash等等。
总结
这个代码块实际上就是针对不同模块规范下,处理了导出的方式。之后开发模块时就可以用上这样的方式,对代码进行导出处理。
模块化的知识点
既然提到了不同模块的规范导出方式不同,那究竟是怎样的不同,这里再稍微记录一下。
JavaScript 模块化的发展历程如下:
无模块化 --> CommonJS规范 --> AMD规范 --> CMD规范 --> ES6模块化。
一、无模块化时期
早期无模块化时,使用 script 标签引入 js 文件,互相之间如果有依赖关系,引入的顺序还需要特别注意,例如 file2.js 依赖 file1.js,那就需要将 file1.js 在 file2.js 之前引入,如下:
<script src="main.js"></script>
<script src="file1.js"></script>
<script src="file2.js"></script>
<script src="file3.js"></script>
缺点很多,这里不多做深入:
- 污染全局作用域
- 维护成本高
- 依赖关系不明显
二、CommonJS 规范
node.js 的模块系统,就是参照 CommonJS 规范实现的。 node 对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性。
基本用法
用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块,注意:这里的加载模块是同步的方式。
这也就可以理解 underscore 判断 commonjs 规范的是通过 module 和 exports 关键字来处理的了。
三、AMD 规范
AMD(Asynchronous module definition),意为“异步的模块定义” ,不同于 CommonJS 规范的同步加载,AMD 正如其名所有模块默认都是异步加载,这也是早期为了满⾜ web 开发的需要,因为如果在 web 端也使⽤同步加载,那么⻚⾯在解析脚本⽂件的过程中可能使⻚⾯暂停响应。
基本用法
AMD 规范中,定义了下面三个API:
define(id, [depends], callback)
require([module], callback)
require.config()
通过 define 来定义一个模块,然后使用 require 来加载一个模块, 使用require.config() 指定引用路径。
四、CMD 规范
CMD(Common Module Definition) ,意为“通用模块定义”,它提供了模块定义和按需加载执行模块。该规范明确了模块的基本书写格式和基本的交互规则。
CMD 和 AMD 都是针对浏览器端的。AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。
五、ES6模块化(ESM)
基本用法
- 使用 import 关键字引入模块,通过 export 关键字导出模块,功能较之于前几个方案更为强大
- ES6 目前在许多浏览器中已经开始支持了,在不支持的浏览器中还是需要通过 babel 将不被支持的 import 编译为当前受到广泛支持的 require[] 。
六、UMD 模块
UMD(Universal Module Definition),意为“通用模块定义规范”。随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJS、CMD 甚至是 AMD 的项目中运行。
这也就是开头看到的那段代码的用意,实际上就是 amd + commonjs + 全局变量 这三种风格的结合来对当前运行环境的判断,如果是 Node 环境 就是使用 CommonJS 规范, 如果不是就判断是否为 AMD 环境, 最后导出全局变量:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.libName = factory());
}(this, (function () { 'use strict';})));
有了 UMD 后我们的代码可同时运行在 Node 和 浏览器上所以现在前端大多数的库最后打包都使用的是 UMD 规范。
相关链接
[1] AMD规范定义: github.com/amdjs/amdjs…
[2] CMD 规范定义: github.com/cmdjs/speci…
[3] AMD和CMD的区别:github.com/seajs/seajs…
[6] CommonJS:www.commonjs.org/
[4] CommonJS Modules/1.1: wiki.commonjs.org/wiki/Module…
[5] UMD规范定义:github.com/umdjs/umd