简析-CommonJS&AMD&CMD

带你走进 JS 规范的历史长河…

大概说下三者区别:

历史流水线:

CommonJS —> AMD —> CMD

共同点:

  1. 都是使用字符串命名方式,让模块作用域只存在于当前模块作用域内,解决了命名空间的问题,且遵循一个模块代表一个文件的理念,将一个js系统地“分割“成多个模块,实现模块化系统。

  2. AMD和CMD都是向着CommonJS致敬的,都有CommonJS的身影。

不同点:

  1. CommonJS起初为了解决,服务端的代码,把代码全都挤在一个文件内,从而导致文件复杂臃肿的,浏览器加载该JS文件,产生卡死;全局作用域污染等问题,而应运而生的模块化开发理念。而且其加载模块的方式属于同步的,需要遵循“先加载,停顿,再执行“的顺序来执行代码,因此很受网速限制。

  2. AMD规范,虽然延续了CommonJS的理念,模块化开发,但不同的是,AMD遵循的是异步加载模块的规范。其加载依赖模块方式是属于依赖前置,即先加载需要的依赖模块,再执行回调函数,大大提高了效率。

  3. CMD规范,是由国内前端大神,玉伯,编写的一个js库 — sea.js,在推过过程,提出的一个基于CommonJS的新规范—CMD。该规范与AMD类似,写法也类似。但不同的是,CMD遵循着依赖后置的理念。即AMD是一次性加载完该模块所需要的所有模块,再执行回调。而CMD是按需加载,即需要用到的时候,才去加载对应模块。

  4. 虽说AMD和CMD代表分别是requireJS和seaJS,但要清楚一点,前面2个是规范,后面2个是库,只是模块加载器。


1. CommonJS

介绍 :
CommonJS,期初名字是叫SeverJS,后改为CommonJS。其定义了服务器端模块的规范,记住,只是规范,由Node推广使用(适用于服务端)。

CommonJS 通过定义一些常见应用程序的API的方式,为模块化开发 JS 提供了一个标准库(后来逐渐形成了规范),用来兼容不同JS解释器和宿主环境。

其目标是提供一个标准库,这个库类比于python,java…

主要作用是为一些特定对象,定义一些API,提供module.exports来导出模块,require来导入模块。

代表:

NodeJS

背景:

由于初期js的局限性,项目累计起来,就会所有代码写进一个文件内,容易导致文件过大,加载太久,浏览器容易卡死,关键以前网速和现在千差万别;

不仅如此,还会导致作用域污染的问题(全局污染),形成命名空间。而且也不方便与其它项目的系统进行进行交互(这里主要指基于浏览器的应用)。

因此,模块化开发成了必然趋势,于是便有了Common Js。

然而在2009年,一位美国佬创建了一个语言上基于js,规范遵循着时下流行且实用的CommonJS规范的Node JS,更是将CommonJS推向了高潮,得益于node Js的火热。

主要API:

1
2
1. module.exports      ------      用于导出当前模块。
2. require ------ 这个require和AMD的require不同。

优点:

  1. 模块化,遵循一个模块一个文件的原则。

  2. 耦合低,不容易导致全局变量的污染,因为每个模块都相当于闭包,都存在于自己的命名空间模块内的作用域。因此,外部无法访问内部私有变量,。

  3. 单一原则。每个模块内部都包含一个自执行函数,而这里提供2个变量,一个exports,一个module,但是最后输出的值,是在module.exports。

  4. 统一性,都使用module.exorts或exports来导出模块,然后使用require()函数来导入模块(同步)。

  5. 高度复用,可以将别人的项目内某个功能之间导入使用。(前提是用module.exports或exports导出)。

缺点:

  1. 不兼容浏览器,不能直接使用。因为浏览器不支持global,module,require,exports这四个全局变量。

  2. 因为期初CommonJS的出现是应用在服务端,而且它的加载方式属于同步的,注意,是加载时是同步的。因此,编码时必须先去加载模块,再执行,这样会影响后面的代码的执行时间,形成阻塞。


2. AMD

背景:

由于出现了CommonJS规范,而且由于NodeJS的推广的原因,使得JS在服务端大放光彩,能干很多其他语言能干的事(java,python等)。

但是(AMD出现的原因),这只是服务端的,毕竟客户端还是不能直接支持CommonJS规范的(至于为什么不支持查看CommonJS介绍)。而JS宿主就是以浏览器为大头的,哪能不行呢?

所以,时代造英雄,AMD英雄降临了。

介绍:

AMD的出现主要解决客户端的模块化开发问题,而且 AMD又延续CommonJS规范的精神,都是模块化开发,都是将模块作用域定义在命名空间内,防止了全局变量的污染。

但同时也很大不一样。

AMD:“因为我是异步加载模块的!!!“

代表:

require.JS

解决了什么问题?

AMD 定义了一套 JS 模块依赖的异步加载标准,来解决同步加载的问题。

主要API:

  1. (导出) 定义模块:

    1
    2
    3
    4
    5
    // 其实有很多方法,但该方法也是官网推荐用法:
    // 匿名定义模块:
    define([依赖1,依赖2...],()=> {
    // 依赖加载完后,执行回调函数...
    })
  2. 导入模块:

    1
    2
    3
    4
    // 这点和commonJS的require有点不一样,AMD需要2个参数,且有回调。
    const 变量 = require([模块名],function() {
    // 执行回调操作...
    })

优点:

  1. 模块化,遵循CommonJS理念,一个文件即模块。

  2. 低耦合,由于采用命名空间内作用域有效,所有外部无法访问私有变量。

  3. 统一性,都由全局变量define函数(导出)定义模块,全局变量require函数导入模块。

  4. 高效性,由于采用的是异步加载的方式来加载模块,加载方法和CommonJS “一样“,都是使用require来加载模块。但是AMD的require有点不一样,AMD的require导入模块时是异步的,而且语法是:require([依赖1,依赖2…],function() {})。也就是说,要先加载完所有依赖,才执行回调函数,而该回调回调函数的参数,也必须严格按照数组内的模块顺序来作为参数,回调函数内都是依赖于这些模块的逻辑代码。那些和这些依赖无关的,可以写在外面,在加载依赖时,是不会影响接下来的代码的。这也就对应了:异步加载,是不会影响后面的代码的运行的。

缺点:

  1. 关系前置,我个人称为 “依赖前置“ 。可能由于设计思想的原因,AMD虽说是异步加载模块,但却是一次性加载完了对应模块,再执行回调。虽说没毛病,我只有等我需要的装备都齐了,才能打boss,这也说的过去。但是从人性化来看,就不怎么好了,我希望我想加载哪个的时候,再加载,不想加载的时候,你就别动。这点可以查看下面CMD的优点。

  2. 写法比较轻浮,光是定义模块就有好几种写法(虽说官网有推荐写法—定义模块)。但这对初学者的我,会产生很多疑惑,也浪费了很多比较的时间。


3. CMD

介绍:

CMD,即Common Module Definition,通用模块定义。

背景:

由于AMD的出现,模块化开发不再是服务端独有的了,浏览器也能实现模块化开发了。但带来便捷的同时,自然也有需要诟病的地方。
比如前面说的,由于设计理念上,AMD是遵循 关系前置,也就是依赖前置的规范,来加载模块。这就导致了一个问题,不够人性化,体现不了按需加载的feel~

因此,国内牛人就AMD基础上,出了个seaJS,同时在推广过程,衍生出一种新的规范—CMD。
从字面意思就能大概猜出,CMD是比较贪心的啊,想做通用的规范哦。
但从语法上看,却不是。
seaJS,其职责单一,严格遵循一个模块就是一个文件的理念,是更接近CommonJS的理念的(具体哪个理念自己查)。

上面说到就AMD基础上出的seaJS,那就是有相同之处了,这里只说不同之处。

CMD 特别就在于,体现了按需加载的理念。
能做到用同步的写法,实现出按需加载的效果。
具体看下面API介绍。

主要API:

  1. define。
    该全局变量是一个函数,该函数自带3个参数,分别是require,exports,module,用来定义模块用。
    其和AMD相同的是,define函数都可以带回调函数,都是可以实现依赖前置的 。
    但和AMD不同的是,遵循CMD规范的 JS ,是可以 依赖后置 的,就是我可以在回调函数里再去加载模块:
    下面说明下三个参数(require, exports, module)的用处:
    (注意,在使用任何一种方法前,都把其他方法注释了!!!)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    	// 1. require:
    // 在定义时可以去加载需要的依赖:
    define(function(require,exports,module) {
    // 按需加载模块:
    let a = require(./b)
    })

    // 2. exports
    // exports 是一个对象,用来向外提供模块接口:
    define(function(require,exports,module) {
    // 按需加载模块:
    let a = require(./b)
    let a_obj = {
    a: a
    }
    // 导出方法:
    // 方法01:
    exports.a = a;
    // 或者导出一个function也是可以的.

    // 方法02:
    // 使用return来直接代替exports
    return {
    a: a
    }

    // 这里实例一个错误的写法,概括为:不能直接给exports赋值,因为最后导出的是module.exports,这样是不会改变模块的值的:
    // 错误:
    exports = {
    a: a
    }
    // 正确:
    module.exports = {
    a: a
    }
    // 3. module:
    module.exports必须是同步执行,不能放在回调里执行:
    // 比如定时器:
    // 错误用法
    setTimeout(function() {
    module.exports = { a: "hello" };
    }, 0);

    })

优点:

  1. 依赖后置,符合按需加载的设计理念,也符合SPA应用的首屏解决方案。
  2. 职责单一,理解简单。

缺点:

  1. 能想到的估计就是API太普通了,简直和AMD差不多的设计,学过require.JS的来㛑sea.js估计没压力,直接把数组去了,改为require了事了。

觉得我说的不对的欢迎扫描二维码来告诉我,十分感谢!!!


个人公众号二维码

Author: JawQ_
Link: http://wujiaqiang.com/2018/01/06/specification/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.
支付宝-打赏
微信-打赏