说到前端模块化,你第一韶光能想到的是什么?Webpack?ES6 Module?还有吗?我们一起来看一下下图。

相信大伙儿对上图的单词都不陌生,可能用过、看过或者是只是听过。
那你能不能用一张图梳理清楚上述所有词汇之间的关系呢?我们日常编写代码的时候,又和他们之间的谁谁谁有关系呢?一、千丝万缕

为了更贴合我们的日常开拓场景(前后端分离),我们考试测验先从不同平台的维度区分,作为本文的切入点。

php前端模块化WEB 前端模块化都有什么 Angular

1. 根据平台划分

平台 规范 特性 浏览器 AMD、CMD 存在网络瓶颈,利用异步加载 非浏览器 CommonJS 直接操作 IO,同步加载 可以看到我们非常暴力的以是不是浏览器作为划分标准。
仔细剖析一下,他们之间最大的差异在于其特性上,是否存在瓶颈。
例如说网络性能瓶颈,每个模块的要求都须要发起一次网络要求,并等待资源下载完成后再进行下一步操作,那全体用户体验是非常糟糕的。
根据该场景,我们简化一下,以同步加载和异步加载两个维度进行区分。

特性 规范 同步加载 CommonJS 异步加载 AMD、CMD 2. AMD、CMD 两大规范

先忽略 CommonJS,我们先先容下,曾经一度盛行的 AMD、CMD 两大规范。

规范 约束条件 代表作 AMD 依赖前置 requirejs CMD 就近依赖 seajs AMD、CMD 供应了封装模块的方法,实现语法上附近,乃至于 requirejs 在后期也默默支持了 CMD 的写法。
我们用一个例子,来讲清楚这两个规范之间最大的差异:依赖前置和就近依赖。

AMD:

// hello.jsdefine(function() { console.log('hello init'); return { getMessage: function() { return 'hello'; } };});// world.jsdefine(function() { console.log('world init');});// maindefine(['./hello.js', './world.js'], function(hello) { return { sayHello: function() { console.log(hello.getMessage()); } };});// 输出// hello init// world init复制代码

CMD:

// hello.jsdefine(function(require, exports) { console.log('hello init'); exports.getMessage = function() { return 'hello'; };});// world.jsdefine(function(require, exports) { console.log('world init'); exports.getMessage = function() { return 'world'; };});// maindefine(function(require) { var message; if (true) { message = require('./hello').getMessage(); } else { message = require('./world').getMessage(); }});// 输出// hello init复制代码

结论: CMD 的输出结果中,没有打印\"大众world init\"大众。
但是,须要把稳的是,CMD 没有打印\公众world init\"大众并是不 world.js 文件没有加载。
AMD 与 CMD 都是在页面初始化时加载完成所有模块,唯一的差异便是就近依赖是当模块被 require 时才会触发实行。

requirejs 和 seajs 的详细实现在这里就不展开阐述了,有兴趣的同学可以到官网理解一波,毕竟现在利用 requirejs 和 seajs 的该当很少了吧。

3. CommonJS

回到 CommonJS,写过 NodeJS 的同学对它肯定不会陌生。
CommonJS 定义了,一个文件便是一个模块。
在 node.js 的实现中,也给每个文件授予了一个 module 工具,这个工具包括了描述当前模块的所有信息,我们考试测验打印 module 工具。

// index.jsconsole.log(module);// 输出{ id: '/Users/x/Documents/code/demo/index.js', exports: {}, parent: { module }, // 调用该模块的模块,可以根据该属性查找调用链 filename: '/Users/x/Documents/code/demo/index.js', loaded: false, children: [...], paths: [...]}复制代码

也便是说,在 CommonJS 里面,模块是用工具来表示。
我们通过“循环加载”的例子进行来加深理解。

// a.jsexports.x = 'a1';console.log('a.js ', require('./b.js').x);exports.x = 'a2';//b.jsexports.x = 'b1';console.log('b.js ', require('./a.js').x);exports.x = 'b2';//mainconsole.log('index.js', require('./a.js').x);// 输出b.js a1a.js b2index.js a2复制代码

我们的理论依据是模块工具,根据该依据我们进行如下剖析。

1、 a.js准备加载,在内存中天生module工具moduleA2、 a.js实行exports.x = 'a1'; 在moduleA的exports属性中添加x3、 a.js实行console.log('a.js', require('./b.js').x); 检测到require关键字,开始加载b.js,a.js实行停息4、 b.js准备加载,在内存中天生module工具moduleB5、 b.js实行exports.x = 'b1'; 在moduleB的exports属性中添加x6、 b.js实行console.log('b.js', require('./a.js').x); 检测到require关键字,开始加载a.js,b.js实行停息7、 检测到内存中存在a.js的module工具moduleA,于是可以将第6步算作console.log('b.js', moduleA.x); 在第二步中moduleA.x赋值为a1,于是输出b.js, a18、 b.js连续实行,exports.x = 'b2',改写moduleBexports的x属性9、 b.js实行完成,回到a.js,此时同理可以将第3步算作console.log('a.js', modulerB.x); 输出了a.js, b210、 a.js连续实行,改写exports.x = 'a2'11、 输出index.js a2复制代码

至此,“CommonJS 的模块,是一个工具。
”这个观点大伙儿该当能理解吧?

回到这个例子,例子里面还涌现了一个保留字 exports。
实在 exports 是指向 module.exports 的一个引用。
举个例子可以解释他们两个之间的关系。

const myFuns = { a: 1 };let moduleExports = myFuns;let myExports = moduleExports;// moduleExports 重新指向moduleExports = { b: 2 };console.log(myExports);// 输出 {a : 1}// 也便是说在module.exports被重新复制时,exports与它的关系就gg了。
办理方法便是重新指向myExports = modulerExports;console.log(myExports);// 输出 { b: 2 }复制代码

4. ES6 module

对 ES6 有所理解的同道们该当都清楚,web 前端模块化在 ES6 之前,并不是措辞规范,不像是其他措辞 java、php 等存在命名空间或者包的观点。
上文提及的 AMD、CMD、CommonJS 规范,都是为了基于规范实现的模块化,并非 JavaScript 语法上的支持。
我们先大略的看一个 ES6 模块化写法的例子:

// a.jsexport const a = 1;// b.jsexport const b = 2;// mainimport { a } from './a.js';import { b } from './b.js';console.log(a, b);//输出 1 2复制代码

emmmm,没错,export 保留字看起来是不是和 CommonJS 的 exports 有点像?我们考试测验 下从保留字比拟 ES6 和 CommonJS。

保留字 CommonJS ES6 require 支持 支持 export / import 不支持 支持 exports / module.exports 支持 不支持 好吧,除了 require 两个都可以用之外,其他实际上还是有明显差别的。
那么问题来了,既然 require 两个都可以用,那这两个在 require 利用上,有差异吗?

我们先比拟下 ES6 module 和 CommonJS 之间的差异。

模块输出 加载办法 CommonJS 值拷贝 工具 ES6 引用(符号链接) 静态解析 又多了几个新颖的词汇,我们先通过例子来先容一下值拷贝和引用的差异。

// 值拷贝 vs 引用// CommonJSlet a = 1;exports.a = a;exports.add = () => { a++;};const { add, a } = require('./a.js');add();console.log(a); // 1// ES6export const a = 1;export const add = () => { a++;};import { a, add } from './a.js';add();console.log(a); // 2// 显而易见CommonJS和ES6之间,值拷贝和引用的差异吧。
复制代码

静态解析,什么是的静态解析呢?差异于 CommonJS 的模块实现,ES6 的模块并不是一个工具,而只是代码凑集。
也便是说,ES6 不须要和 CommonJS 一样,须要把全体文件加载进去,形成一个工具之后,才能知道自己有什么,而是在编写代码的过程中,代码是什么,它便是什么。

PS:

目前各个浏览器、node.js 端对 ES6 的模块化支持实际上并不友好,更多实践同道们有兴趣可以自己搞一波。
在 ES6 中利用 require 字样,静态解析的能力将会丢失!

5. UMD

模块化规范中还有一个 UMD 也不得不提及一下。
什么是 UMD 呢?

UMD = AMD + CommonJS复制代码

没错,UMD 便是这么大略。
常用的场景便是当你封装的模块须要适配不同平台(浏览器、node.js),例如你写了一个基于 Date 工具二次封装的,对付韶光的处理工具类,你想推广给卖力前端页面开拓的 A 同学和后台 Node.js 开拓的 B 同学利用,你是不是就须要考虑你封装的模块,既能适配 Node.js 的 CommonJS 协议,也能适配前端同学利用的 AMD 协议?

二、工具时期

1. webpack

webpack 兴起之后,什么 AMD、CMD、CommonJS、UMD,彷佛都变得不主要了。
由于 webpack 的模块化能力真的强。

webpack 在定义模块上,可以支持 CommonJS、AMD 和 ES6 的模块声明办法,换句话说,便是你的模块如果是利用 CommonJS、AMD 或 ES6 的语法写的,webpack 都支持!
我们看下例子:

//say-amd.jsdefine(function() { 'use strict'; return { sayHello: () => { console.log('say hello by AMD'); } };});//say-commonjs.jsexports.sayHello = () => { console.log('say hello by commonjs');};//say-es6.jsexport const sayHello = () => { console.log('say hello in es6');};//mainimport { sayHello as sayInAMD } from './say-amd';import { sayHello as sayInCommonJS } from './say-commonjs';import { sayHello as sayInES6 } from './say-es6';sayInAMD();sayInCommonJS();sayInES6();复制代码

不仅如此,webpack 识别了你的模块之后,可以将其打包成 UMD、AMD 等等规范的模块重新输出。
例如上文提及到的你须要把 Date 模块封装成 UMD 格式。
只须要在 webpack 的 output 中添加 libraryTarget: 'UMD'即可。

2. more...

总结

回到开始我们提出的问题,我们考试测验利用一张图汇总上文提及到的一溜模块化干系词汇。