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属性的时候,加载完成后立即执行。

【完】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值