js模块化commonJS、AMD、CMD、ES6 Modules

861 阅读6分钟

CommonJS

CommonJS的流行得益于Node.js采用了这种规范 CommonJS规范允许我们:

  • 使用 module.exports = {} 或者 exports.name = xxx 导出模块
  • 使用 const m1 = require('./m1') 引入模块

1.暴露模块的方法:

  • module.exports = {}
  • exports.xxx = 'xxx'
1module.exports = {  
        name'lindaidai',  
        sex'boy'  
    }
(2exports.name = 'lindaidai';  
    exports.sex = 'boy' 

2.引入模块:全局方法require()

注意:⚠️这个全局方法是 node 中的方法哈,它不是 window 下面的,所以如果你没做任何处理想直接在 html 里用肯定就是不行的了。require() 它是 Node.js 中的一个全局方法,并不是CommonJS独有的,CommonJS只是众多规范中的其中一种

3.引入模块分类

  • 核心模块(Node.js自带的模块)
  • 路径模块(相对或绝对定位开始的模块)
  • 自定义模块(node_modules里的模块)
// 直接导入   `Node.js`自带
const path = require('path');  
// 相对路径  
const m1 = require('./m1.js');  
// 直接导入  `node_modules`里的模块
const lodash = require('lodash');  

4.CommonJS规范的特点

  • 所有代码都运行在模块作用域,不会污染全局作用域;
  • 模块是同步加载的,即只有加载完成,才能执行后面的操作;
  • 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
  • CommonJS输出是值的拷贝(即,require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。
  • 代码可复用于Node.js环境下运行
  • 通过NPM发布的很多第三方模块都采用了CommonJS规范 缺点: 代码无法直接运行在浏览器环境下,需要通过工具转换为标准的ES5
CommonJS 还可以细分为 CommonJS1CommonJS2,区别在于 CommonJS1 只能通过 `exports.XX = XX` 的方式导出,CommonJS2CommonJS1 的基础上加入了 `module.exports = XX` 的导出方式。
CommonJS 通常指 CommonJS2

AMD

与CommonJS最大的不同是采用异步的方式加载依赖的模块。

优点:

  • 可在不转换代码的情况下直接在浏览器中运行;
  • 可异步加载依赖;
  • 可并行加载多个依赖;
  • 代码可运行在浏览器环境和 Node.js 环境下。 缺点: JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用(requirejs)

整体使用:

  • define() 定义模块
  • require() 导入模块
// **math.js**
define(function () {  
  var add = function (a, b) {  
    return a + b;  
  }  
  return {  
    add: add  
  }  
}) 
// **test.js**
var requirejs = require("requirejs"); //引入requirejs模块  
  
requirejs(['math'],function(math) {  
  console.log(math)  
  console.log(math.add(12));  
})  

1.产生原因

因为CommonJS是同步加载的,上一个加载完才执行后面的内容,这样可能会造成卡顿,而AMD就是优化这种现象的异步加载(采用异步的方式加载模块)。

2.定义并暴露模块

前提:必须依赖其他库,目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js define(id?, dependencies?, factory)

define(function () {  
  var add = function (a, b) {  
    return a + b;  
  }  
  return {  
    add: add  
  }  
}) 

3.引用模块

var requirejs = require("requirejs"); //引入requirejs模块  
  
requirejs(['math'],function(math) {  
  console.log(math)  
  console.log(math.add(12));  
})  

4.依赖其他模块的define

// m1.js
define(function () {  
  console.log('我是m1, 我被加载了...')  
  return {  
    name'lindaidai',  
    sex'boy'  
  }  
})
// math.js
define(['m1'], function (m1) {  
  console.log('我是math, 我被加载了...')  
  var add = function (a, b) {  
    return a + b;  
  }  
  var print = function () {  
    console.log(m1.name)  
  }  
  return {  
    add: add,  
    printprint  
  }  
}) 
// test.js
var requirejs = require("requirejs"); //引入requirejs模块  
  
requirejs(['math'],function(math) {  
  console.log('我是test, 我被加载了...')  
  console.log(math.add(12));  
  math.print();  
})  
function other () {  
  console.log('我是test模块内的, 但是我不依赖math')  
};  
other(); 

node test.js 结果:

LinDaiDaideMBP:commonJS lindaidai$ node test.js  
我是test模块内的, 但是我不依赖math  
我是m1, 我被加载了...  
我是math, 我被加载了...  
我是test, 我被加载了...  
3  
lindaidai  

CMD

1.定义模块 和AMD定义模块一样:define(id?, dependencies?, factory) 区别在于:

  • AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  • CMD推崇就近依赖,只有在用到某个模块的时候再去require 例子:

AMD写法:

// math.js
define(['m1'], function (m1) {  
  console.log('我是math, 我被加载了...')  
  var add = function (a, b) {  
    return a + b;  
  }  
  var print = function () {  
    console.log(m1.name)  
  }  
  return {  
    add: add,  
    printprint  
  }  
}) 

CMD写法(seajs推荐写法):

define(function (requireexportsmodule) {  
  console.log('我是math, 我被加载了...')  
  var m1 = require('m1');  
  var add = function (a, b) {  
    return a + b;  
  }  
  var print = function () {  
    console.log(m1.name)  
  }  
  module.exports = {  
    add: add,  
    print: print  
  }  
}) 

执行结果区别:

  • AMD,会先加载m1"我是m1"会先执行
  • CMD,我是"我是math"会先执行,因为本题中console.log('我是math, 我被加载了...')是放在require('m1')前面的。

ES6 Modules

es6 modules 已成为了前端模块化的主流规范

1.export导出模块

两种导出方式:

  • 命名式导出(名称导出)
  • 默认导出(自定义导出)

命名式导出 示例:

// 以下两种为错误  
// 1.  
export 1;  
// 2.  
const a = 1;  
export a;  
  
// 以下为正确  
// 1.  
const a = 1;  
export { a };  
  
// 2. 接口名与模块内部变量之间,建立了一一对应的关系  
export const a = 1, b = 2;  
  
// 3. 接口名与模块内部变量之间,建立了一一对应的关系  
export const a = 1;  
export const b = 2;  
  
// 或者用 as 来命名  
const a = 1;  
export { a as outA };  
  
const a = 1;  
const b = 2;  
export { a as outA, b as outB };  

默认导出:在export后面加一个export

// 1.  
const a = 1;  
export default a;  
  
// 2.  
const a = 1;  
export default { a };  
  
// 3.  
export default function() {}; // 可以导出一个函数  
export default class(){}; // 也可以出一个类  

默认导出可以理解为另一种形式的命名导出,也就是把属性名默认重写成了default

const a = 1;  
export defalut a;  
// 等价于  
export { a as default }  

2.import导入模块

也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。

// 某个模块的导出 moudule.js  
export const a = 1;  
  
// 模块导入  
// 1. 这里的a得和被加载的模块输出的接口名对应  
import { a } from './module'  
  
// 2. 使用 as 换名  
import { a as myA } from './module'  
  
// 3. 若是只想要运行被加载的模块可以这样写,但是即使加载2次也只是运行一次  
import './module'  
  
// 4. 整体加载  
import * as module from './module'  
  
// 5. default接口和具名接口  
import module, { a } from './module' 

3.export...from...

export { someVariable } from './a'; 例子:a,b,c三个模块,c想通过b引用a

// b.js
方式一:
import { someVariable } from './a';  
  
export { someVariable }; 
方式二:
export { someVariable } from './a';

4.ES6 Modules规范的特点

  • 导出用export
  • 导入用import
  • 可以用export...from...来中转
  • export命令和import命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,这是因为处于条件代码块中,就没法做静态优化了,违背了ES6模块的设计初衷。
  • import命令具有提升效果,会提升到整个模块的头部,首先执行。
  • 导入的模块变量是不可重新赋值的,只是个可读引用,不过却可以改写属性

缺点: 目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行

ES6 Modules和CommonJS的区别