前端模块化历程 
前言 
在李录的《文明、现代化、价值投资与中国》里,他把人类的文明史分为三大跃升阶段:即 1.0 狩猎采集文明、2.0 农业文明和 3.0 科技文明。针对 JavaScript 的模块化,笔者认为模块化历程也可类比为这样的历程。在前端页面不复杂的情况下,我们只需引入需要的库、js 文件或模块,这就像“狩猎采集文明”时,饿了去打猎、摘果子一样,及时反馈,速度 NO1;后来NodeJS 兴起,让前端有了操作文件的能力,这时候就进入了农业文明时代,这里有两个标准最为突出,一是 NodeJS 社区推崇的 CommonJS,一个是浏览器推崇的 AMD,两个之所以有所不同,是因为两者所处的位置(环境)不同;之后发展演变,ECMAScript 提出了统一标准 ESM 标准,这也是前端模块化的终点,也就是“3.0 科技文明”时代
我会在每个阶段中讲解这个时代代表性的解决方案和对应的库,让你清晰地了解前端模块化的发展历程
目录 
狩猎采集阶段——IIFE 统治前端工程化
- 文件、注释划分方式
 - 函数方式
 - 命名空间方式
 - IIFE(立即执行函数)
 
农业文明阶段——CommonJS 与 AMD
- CommonJS(NodeJS社区定义)
 - AMD(异步模块定义)
 
科技文明阶段——ESM 延续至今
- ESM
 - 前端打包库的兴起
 
狩猎采集阶段——IIFE 统治前端工程化 
最开始前端是没有模块化这一说法的,但网站脚本变多后,有规模后以及要多人协助开发后才有了模块化开发
文件、注释划分方式 
字面意义,通过文件名、注释来规划你的JS代码
// 某某A模块
function foo() {}
// 某某B模块
function bar() {}优点:极简
缺点:
- 有些功能可能忘了加注释
 - JS 文件会越来越大
 
函数方式 
既然用文件和注释解决不了,那么按照功能点将它划分为多个函数方法,然后在总 main.js 中引入调用方法即可
signInAndOut()
userModule()
frinedModule()
feedsModule()但一个问题:全局变量过多,也许引入的第三方脚本会和你命名的全局变量冲突,所以我们需要减少全局变量
优点:简单
缺点:
- 全局函数名太多了
 - 调用关系复杂,难以调整顺序
 - JS 文件会越来越大
 
命名空间方式 
var app = app || {}
app.user.module1()
app.user.module1.submodule1()
app.user.module2()
app.feeds()
app. signInAndOut()
app.friends()大概2010-2012年的时候有这样写代码的,现在没人这样写了
优点:占用的全局变量少(只有一个 app)
缺点:
- 名字越来越长
 - 依赖关系不清晰
 
IIFE(立即执行函数) 
// 没有名字直接执行
!function user(dom, axios, time) {
    // 内部代码
}(dom, axios, time)
// 有名字通过闭包导出代码
!function (dom, axios, time) {
    // 内部代码
    let uer = {}
    const getUser = () => {
        user = { name: 'johan' }
    }
    const updateUser = () => {
        user.name = 'elaine'
    }
    // 暴露 API
    return {
        getUser,
        updateUser
    }
}(dom, axios, time)优点:
- 可以有名字,也可以没有名字
 - 可以声明依赖
 - 可以选择导出内容
 
缺点:
- 无法处理循环依赖
 
在 NodeJS 出现之前,前端库都是通过 IIFE(立即执行函数)来实现的。关于 IIFE,我们之前在 JavaScript 篇的 立即执行函数(IIFE) 中提到过。他已经完成了一个模块化方案的基本功能,也是现代模块化实现的基石
农业文明阶段——CommonJS 与 AMD 
在上文说了IIFE(立即执行函数)除了无法处理循环依赖外,没有什么大问题,所以在前端(浏览器端)只要不去循环依赖就没有问题(IIFE很好用不需要改革),但是在 NodeJS 社区不能这样,因为 NodeJS 没有 HTML,它不像前端在 HTML 入口处引入 JS 模块就可以,它需要在 main.js 中使用其他js文件的方法,所以 NodeJS 社区就有了 CommonJS,即自己的引入导出功能
CommonJS 
导出模块
exports.getUser = () => {}
const x = () => {}
exports.x = x
// 如果要导出整个模块
module.exports = () => {
    return 'hi'
}引入模块
const user = require('./user.js')
user.getUser()
user.x
// 如果导出整个模块的情况下
user() // hiexports 指向
module.exports,即exports = module.exports
优点:
- 用文件名当名字,不用全局变量
 - 可以声明依赖
 - 可以选择导出内容
 - 可以循环依赖
 
缺点:
- 不支持异步
 
它只能适用于 NodeJS,不适用浏览器,为什么因为引入依赖是同步的,而JS是单线程,但引入依赖时间过长时,页面会有卡顿现象
AMD(异步模块定义) 
翻译:Async Module Definition
AMD 规范是异步加载模块,允许指定回调函数,代表函数库:require.js
require.js 主要解决两个问题:
- 异步加载模块
 - 模块之间依赖模糊
 
特点:依赖必须提前声明好
导出模块
define('user', ['require', 'exports', 'dom', 'axios', 'time'], (require, exports, dom, axios, time) => {
    dom.find('#x')
    axios.get()
    time.now()
    
    exports.getUser = function () {}
})引入模块
define(['require', 'exports', 'user'], (require, exports, user) => {
    user.getUser
})优点:
- 用字符串当名字,不用全局变量
 - 可以声明依赖
 - 可以选择导出内容
 - 可以循环依赖
 - 可以异步
 
缺点:
- 对同步的支持不如 CommonJS
 - 没有写入 ECMAScript 文档
 
CMD 
CMD 是阿里的玉伯提出的,js 的函数为 sea.js 。它与 require.js 最主要的区别是实现了按需加载,推崇依赖就近原则,模块延迟执行
特点: 支持动态引入依赖
define(function (require, exports, moduke) {
  var indexCode = require('./index.js');
});AMD 和 CMD 最大的区别是对依赖模块的执行时机处理不同
AMD:依赖前置,提前执行
CMD:依赖就近,延迟执行
AMD && CMD 背后的实现原理 
一种解决方 案是采用 UMD(Universal Module Definition, https://github.com/umdjs/umd),这种模式的语法有点复杂,它同时支持 AMD 和 CommonJS
科技文明阶段——ESM 延续至今 
ES Modules(ES 模块) 
导入模块
<body>
    <script src="main.js" type="module"></script>	
</body>导出模块
main.js
import { a } from './user.js'
console.log(a)user.js
export const a = 1优点:
- 该支持的都支持
 - 支持静态分析(tree-shaking)
 - 支持按需加载(import())
 
缺点:
- 不支持拼接字符串 
- 与静态分析冲突
 
 - 不支持模块加载 
- 与静态分析冲突
 
 - 不兼容 Node 的 CommonJS 
- 使用 .mjs 后缀
 
 
其模块功能主要由两个命令构成:export和import
前端打包库的兴起 
Webpack-集大成者,使用 webpack 进行统一打包
对标的有 Rollup
总结 
笔者这里按照重点梳理
IIFE(声明即执行的函数表达式),特点:在一个单独的函数作用于中执行代码,避免变量冲突
!(function () {
  return {
    data: [],
  };
})();CommonJS: NodeJS 中自带的模块化
var fs = require('fs');
exports.x = xAMD:使用 requireJS 来编写模块化,特点:依赖必须提前声明好
define('./index.js', function (code) {
  // code 就是index.Js 返回的内容
});CMD:使用 seaJS 来编写模块化,特点: 支持动态引入依赖
define(function (require, exports, moduke) {
  var indexCode = require('./index.js');
});UMD:是 AMD 和 CommonJS 的糅合,跨平台的解决方案
ES Modules:现代浏览器的最终解。可以使用 import 关键字引入模块, 通过export 关键字导出模块
常见面试题 
Q:ES Modules与 CommonJS 模块的差异 
A:CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块是运行时加载,ES6 模块是编译时加载(懒加载的核心)
Q:老浏览器(IE6之类)是不认识 ES Modules 的,那 webpack 编译后的产物 ES Modules 动态引入会变成什么样子 
A:IE6 不支持 ES Modules,所以需要 Babel 将 ES6+ 代码转译成 ES5。动态引入也是 Babel 转译成其他代码(通过将动态引入转化为一个普通的脚本加载调用)
CommonJS 是同步的,因为它适用的地方是服务器端,读取文件都是毫秒级别,不需要等待多久时间,而AMD 适用的场景是浏览器端,它必须异步,因为JS是单线程,如果引入的依赖时间过长,页面会出现卡顿现象,不利于交互。所以 AMD 代码看起来很丑(因为有回调)
而 ES Modules 它虽然写的是同步的import a from './a',但它执行时浏览器引擎知道你是 module 代码的话就等你加载完在执行,那为什么它不会慢呢?它按需加载以及会进行静态分析
静态分析减少了不用用到的代码
按需加载,只执行这个页面所需要的代码模块

