CommonJS,AMD,CMD和ES6的模块

在JavaScript中,模块是把实现某个特定功能的代码放在一起并包装,实现解耦和复用。
在ES6之前,ECMA Script并不包含模块的概念,自然也没有模块语法,但的确有一些通用的模式来创建模块,比较流行的就是CommonJS,AMD以及CMD。在ES6中,引入了模块的语法。
本文讲按照时间顺序,依次介绍上述几种模块。

最早的模块模式

在没有CommonJS、AMD之前,开发者也会想一些办法,将工程中的代码进行解耦和封装。比如使用IIFE来封装模块,如下:

var user = function(){
  var status = 'logout'; //私有变量
  var name = 'zhang san'; //私有变量
  function generateUserAvatar() {  // 私有函数
    // logic code
    return 'avatar icon';  
  }
  return { // 暴露给外部的函数或对象
    getUserInfo: function() {
      console.log("UserName is " + name + ";" + generateUserAvatar());
    },
    checkUserLoginStatus: function() {
      return status;
    }
  }
}();
user.getUserInfo();  // UserName is zhangsan
user.checkUserLoginStatus(); // logout

如上面的代码所示,IIFE可以创建一个封闭的作用域,形成一个模块,内部的变量和函数都是私有的,外部不可访问,这样就可以把模块自身的一些逻辑封装在IIFE内部。IIFE return的对象是暴露出来可以被其他模块访问的部分,从而实现了模块间一些逻辑的共享。

CommonJS

在SPA(单页面应用)出现之前,一个web应用由很多单独的页面组成,每个页面的逻辑都相对简单,使用一些简单的模块创建方法就可以很好的构建这些页面的逻辑了。直到JavaScript被应用到服务器端,事情发生了变化。在服务器端,逻辑就复杂多了,也不能像前端一样各个页面去分担不同的功能,所以必须要有一种模块化的方式来管理代码。NodeJS选择了CommonJS作为它的模块化的规范。

CommonJS使用exports导出需要被其他模块使用的对象;使用require导入需要使用的被其他模块暴露出来的对象。

下面是一个CommonJS的示例:

var todoList= {
  showList: function() {
    var todoItem = require('todoItem'); // 同步的模块加载方式
    if (todoItem.needShowTime) {
      var todoTime = require('todoTime');
      todoTime.showTime();
    }
    console.log('showlist');
  }
};
exports.todoList = todoList;

CommonJS是一种同步的模块加载方式。在执行require(‘todoItem’)的时候,会一直等到todoItem被加载完,才会执行后面的代码。这在服务器端是可行的,因为需要require的资源在本地,所以获取资源并不会有太大的时间消耗。但当开发者想把这种模式应用于前端的时候,发现同步加载不是一个好办法。

在加载require(‘todoItem’)的时候,需要去远端的服务器上获取模块,在同步的情况下,获取模块的过程中,不能执行其他任何操作,就会造成页面的假死,影响用户体验。

AMD

在CommonJS之后,为了创建适合前端的模块化规范,就有了AMD (异步模块定义)。AMD是一种可以进行异步加载的模块化规范,因而它很适用于前端开发。

AMD使用define定义模块;使用require加载依赖。下面的代码实现了CommonJS的示例中的相同功能。

define('todoList', ['todoItem'], (todoItem) => {
  var todoList= {
    showList: function() {
      if (todoItem.showtime) {
        require(['todoTime'], (todoTime) => {
          todoTime.showTime();
        });
        console.log('showlist');
      }
    }
  };
  return todoList;
})

AMD与CommonJS最大的不同体现在require上。在CommonJS中,require方法只有一个参数,就是需要被require的module,而在AMD中,require方法有两个参数,一个是被require的module,一个是callback函数。

AMD在require的模块加载完成后,会调用callback方法。而在获取require的模块的过程中,是可以继续执行后面的代码的,如console.log(‘showlist’);,这样页面就可以继续响应用户的其他操作,这就是AMD异步的加载方式所带来的好处。常用的RequireJS就是这样的一种机制,而AMD是RequireJS在推广过程中对模块定义的规范化产出。

// CommonJS 同步加载模块
var todoTime = require('todoTime');
todoTime.showTime();

// AMD 异步加载模块
require(['todoTime'], (todoTime) => {
  todoTime.showTime();
});

CMD

CMD是SeaJS(来自于淘宝前端团队)在推广过程中对规范化定义的产出。
CMD和AMD一样,都是异步加载模块的规范。当讨论到CMD和AMD的不同时,通常会说AMD是依赖前置,而CMD是依赖就近。下面代码是来自玉伯(SeaJS的作者)在知乎上的解答

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好    
  a.doSomething()    
  // 此处略去 100 行    
  b.doSomething()    
  //...
})

// CMD
define(function(require, exports, module) {   
  var a = require('./a')   
  a.doSomething()   
  // 此处略去 100 行   
  var b = require('./b') // 依赖可以就近书写   
  b.doSomething()   
  // ... 
})

事实上,AMD在后来也实现了依赖就近,文中在讲述AMD时所给的示例,就可以算是一种依赖就近,只是AMD的官方还是比较推荐依赖前置这种写法。

ES6中的模块

ES6新增了模块相关的语法,使得构建模块得到了原生的语法支持。它使用export关键字来导出模块中需要暴露出来的变量或函数;使用import来导入模块的依赖。
下面的示例简单的演示了ES6的模块语法。

// user.js
const old = 20; // 内部变量
export const name = 'Jeo Snow'; // 导出的变量
export function setName(newName) {  // 导出的函数
  name = newName;
}
// profile.js
import {name, setName} from './user.js' // 导入模块
setName('li si');
console.log(name); // li si

ES6只规定了模块的语法,并没有规定模块的加载方式,它希望ES的不同宿主(服务器或者浏览器),可以根据自身的特点去设计加载方式。

Web浏览器的模块加载机制体现在HTML规范中。HTML通过script标签,可以加载脚本文件,或者一段脚本代码,将script标签的type属性设置为module,浏览器就会将这段脚本理解为是一个模块。如下:

<script type="module" src="module1.js"></script>
<script type="module">
  import {name, setName} from './user.js' 
  setName('li si');
  export function dosomething() { // dosomething};
</script>

当script标签被设置为type="module"时,将会自动应用defer属性。具体来说,在浏览器解析HTML的过程中,一旦遇到带有src属性的<script type="mudule">,就开始下载。每个模块在加载的时候,都必须要递归的加载完所有的依赖模块,才算模块加载完成。当所有文档都解析完毕后,再顺序执行各个模块。

因此,上面的代码的加载如下:

  1. 下载并解析module1.js。
  2. 递归下载并解析。
  3. 解析内联模块。
  4. 递归下载并解析内联模块中导入的资源。

执行过程会等到整个文档被解析完毕后,如下:

  1. 递归执行module1.js中导入的资源。
  2. 执行module1.js。
  3. 递归执行内联模块中导入的资源。
  4. 执行内联模块。
    注:当<script type="mudule">带有async属性的时候,加载完成后立即执行。

【完】

### 回答1: CommonJSAMD/CMD是两种不同的模块化规范,而ES6则是JavaScript的新标准,也包含了模块化的支持。 CommonJS主要用于服务器端的模块化,其特点是同步加载模块模块输出的是一个对象,可以通过require()方法加载模块AMD/CMD则主要用于浏览器端的模块化,其特点是异步加载模块模块输出的是一个函数,可以通过define()方法定义模块ES6模块化则是JavaScript的新标准,其特点是静态加载模块模块输出的是一个变量,可以通过importexport语句加载定义模块。 总的来说,CommonJSAMD/CMD是旧的模块化规范,而ES6则是新的标准,具有更好的性能可读性。 ### 回答2: CommonJS是一个模块规范,旨在使JavaScript在服务器端上运行。它在Node.js上得到广泛应用,主要是用于模块管理代码复用。它定义了模块如何定义以及如何导出导入模块AMDCMD是两个常用的模块规范,旨在更好地管理浏览器端的模块AMDCMD规范都优化了服务器端的加载速度,提高了代码复用性。 ES6是一个新版的JavaScript规范,它增加了许多新的语言特性语法糖,使得JavaScript更具有可读性可维护性。ES6规范中引入了模块的概念,通过importexport可以轻松管理模块,并且JS引擎会进行编译优化以提高性能。 CommonJSAMD/CMD的主要区别在于模块的加载方式。CommonJS采用同步加载方式,即导入模块时会等待所有依赖模块都加载完毕后再执行导入操作。这会造成一定的阻塞,但是可以保证依赖关系正确。而AMD/CMD采用异步加载方式,即采用回调函数的方式导入模块,不会造成阻塞,但是需要手动管理依赖关系。 ES6模块的最大优点在于静态编译。在使用ES6模块时,浏览器可以在代码加载时对模块进行静态分析,从而明确哪些模块需要导入导出,它们的依赖关系以及导入的值。这是在CommonJSAMD/CMD模块规范中无法做到的。ES6模块的缺点是目前还不是所有的浏览器都支持。 ### 回答3: CommonJSAMDCMDES6JavaScript使用的模块系统。它们都试图将代码组织为可重用的模块,但它们在一些方面不同。 CommonJS是一个使用Node.js的模块系统,它允许在服务器端客户端共享模块CommonJS模块是同步加载的,这意味着当模块被请求时,它会立即加载模块,并立即执行模块的代码。 AMD(异步模块定义)是在浏览器环境中使用的模块系统,它允许异步加载模块。当一个模块被请求时,AMD并不会像CommonJS那样立即加载它,而是等待其他模块完成加载。然后,当模块被加载运行时,AMD会运行任何模块依赖项的回调函数。 CMD(通用模块定义)是一个应用于浏览器服务器端的模块系统,它的特点是就近依赖,在需要时才进行依赖的加载。CMD模块是通过define函数来定义的。在调用define时,会传递一个回调函数,该回调函数可以使用require来访问其他模块ES6模块JavaScript的原生模块系统,它允许在JavaScript中定义模块ES6模块是静态的,这意味着每个模块都是在编译时确定的。ES6模块支持默认导出命名导出。 在总体上,CommonJS适合于服务器端,AMDCMD适合于浏览器端,而ES6则是一个全面的模块系统,适用于任何环境。不同的模块系统在实现上有所不同,选择哪种类型的模块系统需要根据具体情况进行判断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值