前端模块化进程,commonJS,AMD,CMD对比
commonJS规范
随着前端技术的不断发展,项目越来越大,越来越不好管理,多人开发越来让开发者头疼,于是出现了命名空间,这没有办法的办法,但是所有变量都是全局的话,管理非常混乱,而且在Node 出现前,javascript 在服务器端基本没有市场,经过javascript 不断努力,社区为 javascript 制定了相应的规范。其中 commonJS 规范的提出算是最为重要的里程碑。
commonJS对模块的引用非常简单,主要分为模块引用、模块定义和模块标示3部分
1.模块定义
在模块中,上下文提供了 require() 方法来引入外来模块,引入对应的功能。上下文还提供了 exports 对象,用来导出当前模块的方法或变量。上下文还提供了一个module对象,它代表模块本身,而 exports 是 module 的属性。在node中一个文件就是一个模块,将方法挂载在exports上作为属性即可定义导出方式。
// math.js exports.add = function () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i]; i++ } return sum; }
2.模块引用
在commonJS规范中,存在 require() 方法,这个方法接受模块标示,以此引入一个模块的API到当前的上下文中。
模块引入的实例代码
var math = require('math');
3.模块标示
模块标示其实就是传递给require()的参数,必须符合小驼峰命名的字符串,或者以.,..开头的相对路径,或者绝对路径。
commonJS 和 AMD
CommonJS是主要为了JS在后端的表现制定的,他是不适合前端的,为什么这么说呢?
这需要分析一下浏览器端的js和服务器端js都主要做了哪些事,有什么不同了:
服务器端 | 浏览器js |
---|---|
加载时从磁盘中加载 | 加载时需要通过网络加载 |
相同的代码需要多次执行 | 代码需要从一个服务器端分发到多个客户端执行 |
CPU和内存资源是瓶颈 | 带宽是瓶颈 |
commonJS在服务器端运行完全没有问题的,因为所有资源都在服务器端的磁盘里,加载速度很快。但是在浏览器端因为网速的限制,加载资源需要时间,会阻塞代码的运行,而AMD(异步模块定义)的出现,可以异。它的模块定义如下:
define(id?, dependencies?, factory);
它的模块id和依赖都是可选的,而factory函数的内容就是实际代码内容,假设一个模块不依赖任何一个模块
//math.js define(function () { var math = {}; math.add = function (arr) { for(var i = 0, sum = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } math.muti = function (arr) { for(var i = 0, sum = 0; i < arr.length; i++) { sum *= arr[i]; } return sum; } })
引用的时候这样写
//main.js define(['math', function(math) { var arr = [1, 10, 20], sum = math.add(arr); console.log(sum); //31 }]
当然这是原理,大家明白了之后,对于入门者,估计还是不知道怎么去用,那么我就帮人帮到底(会用的绕过)。
- 引入一个requirejs
<script src="js/require.js"></script>
如果觉得觉得这个文件加载也会堵塞 js 的话就把他放在代码的底部,然后这样写
<script src="js/require.js" defer async="true" ></script>
其中defer是为了兼容IE浏览器。加载完requirejs之后,怎么加载我们自己的js呢
2.加载我们自己的js
<script src="js/require.js" data-main="path/main"></script>
// main.js // 首先用config()指定各模块路径和引用名 require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //实际路径为js/lib/jquery.min.js "underscore": "underscore.min", } }); // 执行基本操作 require(["jquery","underscore"],function($,_){ // some code here });
data-main 属性里面的就是我们自己的入口文件了,由于 requirejs 默认加载 js 文件的,所以 后缀 js 可以省略。OK,下面就可以快乐的写模块了。
CMD规范
CMD规范由国内的玉伯大神指出。是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。
/** AMD写法 **/ define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等于在最前面声明并初始化了要用到的所有模块 a.doSomething(); if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.doSomething() } }); /** CMD写法 **/ define(function(require, exports, module) { var a = require('./a'); //在需要时申明 a.doSomething(); if (false) { var b = require('./b'); b.doSomething(); } }); /** sea.js **/ // 定义模块 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; });
require, exports 和 module 通过形参传递给模块,在需要依赖模块时,随时引入,可以看出,与 AMD 规范相比,CMD 规范更加接近 node 对 commonJS规范的定义。
兼容多种模块规范
技术在发展,优秀的开源项目也在不断更新,比如jquery,underscore。它们很快出了兼容不同规范的版本,那么他们是怎么做到的,是写好几套符合不同规范的代码么,当然不是。那么我们怎么写出像他们那样兼容不同规范的代码呢,很简单,我给出一个例子:
//hello.js ;(function(name, definition){ //检测上下文环境是否为 AMD 或 CMD var hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if (hasDefine) { //AMD 环境或 CMD 环境 define(definition); } else if (hasExports) { // 定义为普通的node模块 module.exports = definition(); } else { //将模块的执行结果挂在 window 变量中,在浏览器中 this 指向 window 对象 this[name] = definition(); } })('hello', function() { //代码主体 var hello = function () {}; return hello; })