javascript的模块化开发规范:AMD、CMD、Commonjs、ES6 Module的理解

200 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情

我们在学习js的过程中,经常会见到多种模块化规范,在此就来详细讲解一下这些规范都是什么和其之间的区别。

一、CommonJS

在ES6之前,没有原生方法支持模块化。所以需要有特定的规范来进行模块化语法的编写,并且需要模块化的工具把这些模块化的语法与js运行时连接起来。

Commonjs规范了同步声明依赖的模块定义。这个规范主要用于nodejs服务端实现模块化。CommonJS模块语法不能在浏览器中直接运行。

语法示例:

// 1. 使用require()指定依赖
var moduleA = require('./moduleA.js')
// 2. 使用exports
module.exports = {
    a: moduleA.name,
    tag: 'xxx'
}

特点:

  1. require()引入依赖是同步的,支持动态依赖;
if (condition) {
    requrie('./moduleA.js')    
}

只有当condition为true时,moduleA模块才会被加载,这个加载是同步的。

  1. 模块永远是单例的:无论一个模块被require多少次,实际只加载一次。模块第一次加载后会被缓存,后续其他的加载会直接使用缓存。
var moduleA = require('./a.js')
var moduleB = require('./a.js')
console.log(moduleA)
console.log(moduleA===moduleB)  // true
moduleA.name = '小明'
console.log(moduleB.name)  // 小明

CommonJS语法不适合于浏览器环境,因为该加载模块的方法是同步的。在服务端所有的模块都存放在本地硬盘,可以同步加载完成,加载的时间就是硬盘读取时间。但是在浏览器中使用该方法加载模块时,因为模块文件都在服务端,要等待网络请求返回文件,需要很长时间,浏览器处于"假死"状态。

因此浏览器的模块加载,就需要异步加载,就出现了AMD。

二、AMD

AMD(Asynchronous Module Definitio)是以浏览器运行环境为目标的模块异步加载规范。由于不是JavaScript原生支持的,所以使用AMD规范进行页面开发需要用到对应的库函数,也就是requireJS

AMD实际上是requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置

AMD中使用define方法用于定义模块,它是全局变量

define(id?, dependencies?, factory);

参数描述:

  • id: 模块的名称。如果没有该参数,模块名默认使用脚本文件名(不带扩展名)。模块名必须是“顶级”的和绝对的(不允许相对名字)。
  • 依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。如果没有该参数,默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,AMD加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
  • 工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
// 定义模块a
define('a',function() {
    // ...
    return {
        num: 1,
        add: funtion() {
            this.num++
            console.log(this.num)
        }
    }
})
// 定义模块b
define('b', ['a'], function(a) {
    // 使用模块a
    a.add();
    // ... 
    return {
        name: 'xx',
        change: funtion() {
            // ...
        }
    }
})

我们在页面上使用requirejs库进行模块化开发时:

// main.js文件
requirejs.config({
    paths: {
        jquery: 'jquery.min' //可以省略.js
    }
});
//引入模块,用变量$表示jquery模块
requirejs(['jquery'], function ($) {
    $('body').css('background-color','red');
});
​
// a.js文件
// 定义模块
define('a',['jquery'], function ($) {//引入jQuery模块
    return {
        add: function(x,y){
            return x + y;
        }
    };
});
​
// 使用
require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));
});

三、CMD

CMD规范规定一个模块就是一个文件。z这个规范明确了模块的基本书写格式和基本交互规则。

CMD规范是国内发展出来的,同样用于浏览器环境的模块化规范。于AMD的区别在于使用的库函数是seaJS

CMDseajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近

define(factory);

factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:requireexportsmodule

// 与AMD规范的define类似
define(function(require, exports, module) {
    var a = require('a')
    a.dosomething()
    // ...
});

使用seajs开发示例:

// 定义模块moduleA.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});
​
// 加载模块
seajs.use(['moduleA.js'], function(a){
    var star= a.data;
    console.log(star);  //1
});

AMD和CMD

我们上面讲到

  1. AMD推崇依赖前置,即一开始就将模块进入,例如a模块和b模块,然后在回调中拿到该模块使用。
  2. CMD是按需加载,依赖就近。即在用到某个模块时再require进来。
// 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提前执行(异步加载)+延迟执行。CMD延迟执行(按需加载,顺序执行)
  • AMD使用requireJS,CMD使用seaJS
  • AMD的API根据使用范围有区别,但使用同一个api接口。CMD每个API的职责单一。

四、ES6 Moudle

es6引入了模块规范,使得原生浏览器支持模块化开发。这就意味着之前的加载器和其他预处理都不再必要了。

// 模块引入
import ...
// 模块导出
export ...

ES6的模块导入导出都不允许放在条件语句中,其导入和导出的顺序也应当注意

// 正确导入
import a from 'a.js'
console.log(a)
// 可以,但应该尽量避免
console.log(a)
import a from 'a.js'
// 条件判断中不允许
if(condition) {
   import ..
}
​
// 正确导出
const num = 1
export num
// 可以,但应该尽量避免
export num
const num = 1
// 条件判断中不允许
if(condition) {
   export ..
}

总结

  1. CommonJs:是服务端规范,同步加载。也就是需要模块加载完成后执行后面的代码。
  2. AMDrequirejs 在推广过程中对模块定义的规范化产出,依赖前置,异步加载。
  3. CMDseajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近
  4. ES6 Module:模块输出的是一个值的引用,编译时输出接口,ES6模块不是对象,它对外接口只是一种静态定义,在代码静态解析阶段就会生成。

区别描述

  • CommonJs 是单个值导出,模块输出的是一个值的 copy,运行时加载,加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成
  • 导出:AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的
  • CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
  • CommonJsthis 是当前模块,ES6 Modulethisundefined
  • AMDCMD是动态引入,运行时才知道的
  • ES6 Module是静态引入,好处方便wepback打包依赖图谱分析
  • CommonJs 是单个值导出,ES6 Module 可以导出多个