模块化是软件系统的属性,这个系统被分解为一组高内聚,低耦合的模块。理想状态下咱们只须要完成本身部分的核心业务逻辑代码,其余方面的依赖能够经过直接加载被人已经写好模块进行使用便可。
一个模块化系统所必须的能力: javascript
定义封装的模块。 定义新模块对其余模块的依赖。 可对其余模块的引入支持。
CommonJS
nodeJs出现后使用了CommonJS规范来解决JS的模块化问题。因为Node.js主要用于服务器的编程,加载的模块文件通常都已经存在本地硬盘,因此加载起来比较快,不用考虑异步加载的方式,因此CommonJS规范也是同步加载依赖模块,加载完后执行后面代码。但若是是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。因此就有了 AMD CMD 解决方案。html
AMD和CMD前端
AMD(Asynchromous Module Definition)是equireJS 在推广过程当中对模块定义的规范化产出。CMD是SeaJS 在推广过程当中对模块定义的规范化产出。RequireJs出自dojo加载器的做者James Burke,SeaJs出自国内前端大师玉伯。两者的区别,玉伯在12年如是说:java
RequireJS 和 SeaJS 都是很不错的模块加载器,二者区别以下:
二者定位有差别。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专一于 Web 浏览器端,同时经过 Node 扩展的方式能够很方便跑在 Node 服务器端.
二者遵循的标准有差别。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不一样,致使了二者 API 的不一样。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
二者社区理念有差别。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
二者代码质量有差别。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。
二者对调试等的支持有差别。SeaJS 经过插件,能够实现 Fiddler 中自动映射的功能,还能够实现自动 combo 等功能,很是方便便捷。RequireJS 无这方面的支持。
二者的插件机制有差别。RequireJS 采起的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采起的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而很是灵活,能够实现各类类型的插件。.
我我的感受requirejs更科学,全部依赖的模块要先执行好。若是A模块依赖B。当执行A中的某个操doSomething()后,再去依赖执行B模块require('B');若是B模块出错了,doSomething的操做如何回滚? 不少语言中的import, include, useing都是先将导入的类或者模块执行好。若是被导入的模块都有问题,有错误,执行当前模块有何意义?node
而依赖dependencies是工厂的原材料,在工厂进行生产的时候,是先把原材料一次性都在它本身的工厂里加工好,仍是把原材料的工厂搬到当前的factory来何时须要,何时加工,哪一个总体时间效率更高?jquery
首先回答第一个问题。git
第一个问题的题设并不彻底正确,“依赖”和“执行”的概念比较模糊。编程语言执行一般分为两个阶段,编译(compilation)和运行(runtime)。对于静态语言(好比C/C++)来讲,在编译时若是出现错误,那可能以前的编译都视为无效,的确会出现描述中须要回滚或者从新编译的问题。但对于动态语言或者脚本语言,大部分执行都处在运行时阶段或者解释器中:假设我使用Nodejs或者Python写了一段服务器运行脚本,在持续运行了一段时间以后由于某项需求要加载某个(依赖)模块,同时也由于这个模块致使服务端挂了——我认为这时并不存在回滚的问题。在加载依赖模块以前当前的模块的大部分功能已经成功运行了。github
再回答第二个问题。web
对于“工厂”和“原材料”的比喻不够恰当。难道依赖模块没有加载完毕当前模块就没法工做吗?requirejs的确是这样的,从上面的截图能够看出,依赖模块老是先于当前模块加载和执行完毕。但咱们考虑一下基于CommonJS标准的Nodejs的语法,使用require函数加载依赖模块能够在页面的任何位置,能够只是在须要的时候。也就是说当前模块没必要在依赖模块加载完毕后才执行。编程
你可能会问,为何要拿AMD标准与CommonJS标准比较,而不是CMD标准?
玉伯在CommonJS 是什么这篇文章中已经告诉了咱们CMD某种程度上遵循的就是CommonJS标准:
从上面能够看出,Sea.js 的初衷是为了让 CommonJS Modules/1.1 的模块能运行在浏览器端,但因为浏览器和服务器的实质差别,实际上这个梦没法彻底达成,也没有必要去达成。
更好的一种方式是,Sea.js 专一于 Web 浏览器端,CommonJS 则专一于服务器端,但二者有共通的部分。对于须要在两端均可以跑的模块,能够 有便捷的方案来快速迁移。
CMD推崇依赖就近,能够把依赖写进你的代码中的任意一行,例:
define(function(require, exports, module) { var a = require('./a') a.doSomething() var b = require('./b') b.doSomething()})
代码在运行时,首先是不知道依赖的,须要遍历全部的require关键字,找出后面的依赖。具体作法是将function toString后,用正则匹配出require关键字后面的依赖。显然,这是一种牺牲性能来换取更多开发便利的方法。
而AMD是依赖前置的,换句话说,在解析和执行当前模块以前,模块做者必须指明当前模块所依赖的模块,表如今require函数的调用结构上为:
define(['./a','./b'],function(a,b){ a.doSomething() b.doSomething()})
代码在一旦运行到此处,能当即知晓依赖。而无需遍历整个函数体找到它的依赖,所以性能有所提高,缺点就是开发者必须显式得指明依赖——这会使得开发工做量变大,好比:当你写到函数体内部几百上千行的时候,突然发现须要增长一个依赖,你不得不回到函数顶端来将这个依赖添加进数组。
细心的读者可能发现,到目前位置我讨论的AMD和CMD的思想的关于依赖的部分,都只讨论的“硬依赖”,也就是执行前确定须要的依赖,可是这不是所有的状况。有的时候状况是这样的:
// 函数体内:if(status){ a.doSomething()}
在这个函数体内,可能依赖a,也可能不依赖a,我把这种可能的依赖成为“软依赖”。对于软依赖固然能够直接当硬依赖处理,可是这样不经济,由于依赖是不必定的,有可能加载了此处的依赖而实际上没有用上。
对于软依赖的处理,我推荐依赖前置+回调函数的实现形式。上面的例子简单表述以下:
// 函数体内:if(status){ async(['a'],function(a){ a.doSomething() })}
至此能够对由commonJS衍生出来的方案作出总结了。在浏览器端来设计模块加载机制,须要考虑依赖的问题。
咱们先把依赖分为两种,“强依赖” —— 确定须要 和“弱依赖” —— 可能须要。
对于强依赖,若是要性能优先,则考虑参照依赖前置的思想设计你的模块加载器;若是考虑开发成本优先,则考虑按照依赖就近的思想设计你的模块加载器。
对于弱依赖,只须要将弱依赖的部分改写到回调函数内便可。
不管AMD与CMD都要面临如下几个问题:
一、模块式如何注册的,define函数都作了什么?
二、他们是如何知道模块的依赖?
三、如何作到异步加载?尤为是seajs如何作到异步加载延迟执行的?
辩证法第一规律:事物之间具备有机联系。AMD与CMD都借鉴了CommonJs,宏观层面必有一致性,好比总体处理流程:
模块的加载解析到执行过程一共经历了6个步骤:
一、由入口进入程序
二、进入程序后首先要作的就是创建一个模块仓库(这是防止重复加载模块的关键),JavaScript原生的object对象最为适合,key表明模块Id,value表明各个模块,处理主模块
三、向模块仓库注册一模块,一个模块最少包含四个属性:id(惟一标识符)、deps(依赖项的id数组)、factory(模块自身代码)、status(模块的状态:未加载、已加载未执行、已执行等),放到代码中固然仍是object最合适
四、模块便是JavaScript文件,使用无阻塞方式(动态建立script标签)加载模块
五、模块加载完毕后,获取依赖项(amd、cmd区别),改变模块status,由statuschange后,检测全部模块的依赖项。
因为requirejs与seajs遵循规范不一样,requirejs在define函数中能够很容易得到当前模块依赖项。而seajs中不须要依赖声明,因此必须作一些特殊处理才可否得到依赖项。方法将factory做toString处理,而后用正则匹配出其中的依赖项,好比出现require(./a),则检测到须要依赖a模块。
六、若是模块的依赖项彻底加载完毕(amd中须要执行完毕,cmd中只须要文件加载完毕,注意这时候的factory还没有执行,当使用require请求该模块时,factory才会执行,因此在性能上seajs逊于requirejs),执行主模块的factory函数;不然进入步骤3。
处理流程摘自:以代码爱好者角度来看AMD与CMD
写法举例
CommonJs
// 文件名: foo.jsdefine(['jquery', 'underscore'], function ($, _) {// 方法function a(){}; // 私有方法,由于没有被返回(见下面)function b(){}; // 公共方法,由于被返回了function c(){}; // 公共方法,由于被返回了 // 暴露公共方法 return { b: b, c: c }});```AMD```javascript// 文件名: foo.jsvar $ = require('jquery');var _ = require('underscore');// methodsfunction a(){}; // 私有方法,由于它没在module.exports中 (见下面)function b(){}; // 公共方法,由于它在module.exports中定义了function c(){}; // 公共方法,由于它在module.exports中定义了// 暴露公共方法module.exports = { b: b, c: c};
CMD
define(function (requie, exports, module) { //依赖能够就近书写 var a = require('./a'); a.test(); ... //软依赖 if (status) { var b = requie('./b'); b.test(); }});
UMD
UMD是AMD和CommonJS的糅合,兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范。
AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫令人们又想出另外一个更通用的模式UMD (Universal Module Definition)。但愿解决跨平台的解决方案。UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
(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 }}));
AMD、CMD、UMD 模块的写法
关于 CommonJS AMD CMD UMD
扩展阅读:
AMD规范文档
amdjs 的 require 接口文档
amdjs 的接口文档
RequireJS官网接口文档
模块系统
前端模块化开发的价值
前端模块化开发那点历史
CMD 模块定义规范
SeaJS API快速参考
从 CommonJS 到 Sea.js
RequireJS和AMD规范
CommonJS规范
Javascript模块化编程
Javascript模块化编程
知乎 AMD 和 CMD 的区别有哪些?
JavaScript模块化开发 - CommonJS规范
JavaScript模块化开发 - AMD规范
模块化设计
模块化