CommonJS、AMD、CMD、ES Module与Node

181 阅读4分钟

CommonJS的概念

  CommonJS是一种规范,Node是这种规范的实现。在Node中如何体现CommonJS规范呢?就在于模块化require()exportsmodule.exports这三者共同实现了这种模块化

   // foo.js
   const name = 'zengge'
   const age = 22
   exports.name = name
   exports.age = age
   // main.js
   const fooObj = require('./foo')

  在foo文件中我们使用exports导出了两个变量nameage,我们在main文件中使用了require()函数引入了foo文件并返回了一个对象,我们用常量fooObj接收,那么现在fooObj就指向了由name='zengge'age=22所组成的对象。

   // main.js
   const fooObj = require('./foo')
   console.log(fooObj) // {name: 'zengge', age: 22}

既然是对象,我们就可以使用解构,也可以这样书写代码:

   // main.js
   const { name, age } = require('./foo')

Node中我们也经常使用module.exports来导出,那module.exports又是什么呢?它和exports的区别是什么呢?
CommonJS中其实并没有module.exports这个概念的,但是Node为了实现模块的导出,Node使用了Module类。 image.png
所以真正导出的并不是export,而是module.exports。那有人会问了,我直接通过exports也可以导出啊,这是因为module.exportsexports实际上指向的是同一个对象,也就是我们刚刚提到的{name: 'zengge', age: 22}这个对象,所以如果将module.exports赋值给一个新的对象,那么exports所指向的对象就会变成module.exports赋值的新对象,从而证实真正的导出者是module.exports

CommonJS的缺点

  我们知道使用CommonJS加载模块是同步的,这在服务器上并不会有什么问题,服务器加载的都是本地文件,加载速度很快。
但是如果将它运用在浏览器上,即使是很简单的DOM操作,也将会阻塞代码的运行,造成浏览器的"假死"

AMD

  所以在早期,浏览器会使用一种叫AMD(Asyncchronous Module Definition)的一种规范,它主要是采用了异步加载模块,可以让代码异步加载,从而解决浏览器"假死"AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

require([module], callback);   第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

   require(['math'], function (math) {
   math.add(2, 3);
   });

  math.add()math模块加载不是同步的,浏览器不会发生"假死"。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.jscurl.js。 事实上AMD规范早于CommonJS,但是CommonJS目前仍然在被使用,而AMD使用的较少了。

CMD

CMDSeaJS在推广过程中对模块定义的规范化产出

CMDAMD的区别有以下几点:

  1. 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

  2. AMD推崇依赖前置(在定义模块的时候就要声明其依赖的模块),CMD推崇依赖就近(只有在用到某个模块的时候再去require——按需加载)。

//AMD
define(['./a','./b'], function (a, b) {
    //依赖一开始就写好
    a.test();
    b.test();
});

//CMD
define(function (requie, exports, module) {
    //依赖可以就近书写
    var a = require('./a');
    a.test();
    ...
    //软依赖
    if (status) {
        var b = requie('./b');
        b.test();
    }
});

ES Module

  在以前Javascript本身并没有模块化,这也一直是一个痛点。所以以上的CommonJS,AMD,CMD这些规范在Javascript中使用,但是ES Module推出之后,一切都变得不一样了。

导出方式
  • 导出单个变量
   export const name = "张三" 
   export function add(a, b) { 
            return a + b;
          } 
   export const obj = { name: 'jack' }
  • 导出多个变量
   const name = "张三"; 
   function add(a, b) { 
     return a + b;
   } 
   const obj = { name: 'jack' };
   export {
     name,
     add,
     obj
   }
  • 导出默认变量
   const name = "张三"; 
   function add(a, b) { 
     return a + b;
   } 
   const obj = { name: 'jack' };
   export default name

默认导出只能有一个

  • 导出时起别名
   const name = "张三"; 
   function add(a, b) { 
     return a + b;
   } 
   const obj = { name: 'jack' };
   export {
     name as tempName
     add as tempAdd
     obj as tempObj
   }
  • exportimport结合导出
   export { name, obj, add } from './foo.js'

常见于封装组件时的统一导出

引入方式
  • 普通引入
   import { name, age, add } from './foo.js'
  • 别名引入
   import { name as tempName, age as tempName, add as tempAdd } from './foo.js'
  • * as foo 引入
   import * as foo from './foo.js'
  • 动态引入
   const importPromise = import('./foo.js')
   importPromise.then((res) => {
     console.log(res.name)
   }).cache((err) => {
     console.log(err)
   })

import的动态引入本质上返回的是一个promise,所以这样就可以使用thencache实现动态引入,也可以使用async/await语法糖。