模块化二—CommonJS、AMD、CMD、ES6模块

一 CommonJS规范:是Node.js使用的模块化规范方式

1 暴露定义的模块:

// m1.js

// 1. 正确  
module.exports = {  
    name: 'lindaidai',  
    sex: 'boy'  
}  
  
// 2. 正确  
exports.name = 'lindaidai';  
exports.sex = 'boy'  
  
// 3. 正确  
module.exports.name = 'lindaidai';  
module.exports.sex = 'boy'  
  
// 4. 无效  
exports = {  
    name: 'lindaidai',  
    sex: 'boy'  
}  

2 引入模块:require()全局方法是 node 中的方法哈,它不是 window 下面的,所以如果你没做任何处理,想直接在 html 里用肯定就是不行的了,只能在node环境中使用该方法。

3 模块标识符:指在引入模块时调用 require() 函数的参数,如:

// 直接导入  
const path = require('path');  
// 相对路径  
const m1 = require('./m1.js');  
// 直接导入  
const lodash = require('lodash');  

    path这种它是Node.js就自带的模块,m1是路径模块,lodash是我们使用npm i lodash下载到node_modules里的模块。模块引入方式分为三类:

  • 核心模块(Node.js自带的模块):查找方式:直接跳过路径分析和文件定位
  • 路径模块(相对或绝对定位开始的模块):查找方式:直接得出相对路径就好了
  • 自定义模块(node_modules里的模块):查找方式(路径分析):先在当前目录的node_modules里找这个模块,如果没有,它会往上一级目录查找,查找上一级的node_modules,依次往上,直到根目录下都没有, 就抛出错误。

3.1文件定位:

在NodeJS路径分析中, 省略了扩展名的文件, 会依次补充上.js, .node, .json来尝试, 如果传入的是一个目录, 那么NodeJS会把它当成一个包来看待, 会采用以下方式确定文件名

第一步, 找出目录下的package.json, 用JSON.parse()解析出main字段

第二步, 如果main字段指定的文件还是省略了扩展, 那么会依次补充.js, .node, .json尝试.

第三步, 如果main字段制定的文件不存在, 或者根本就不存在package.json, 那么会默认加载这个目录下的index.js, index.node, index.json文件.

以上就是文件定位的过程, 再搭配上路径分析的过程, 进行排列组合, 这得有多少种可能呀. 所以说, 自定义模块的引入, 是最费性能的.

4 CommonJS规范特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域;
  • 模块是同步加载的,即只有加载完成,才能执行后面的操作;
  • 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
  • CommonJS输出是值的拷贝(即,require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。

 

二 AMD(Asynchronous Module Definition)异步模块定义,是一种前端模块组织规范:AMD的实现主要有两个: requireJS和curl.js

AMD的理念可以用如下两个API概括: define和require

define方法用于定义一个模块,它接收两个参数:

  • 第一个参数是一个数组,表示这个模块所依赖的其他模块
  • 第二个参数是一个方法,这个方法通过入参的方式将所依赖模块的输出依次取出,并在方法内使用,同时将返回值传递给依赖它的其他模块使用。

 

三 CMD(Common Module Definition)通用模块定义:sea.js是遵循CMD规范实现的模块加载器

和AMD不同的是,CMD没有提供前置的依赖数组,而是接收一个factory函数,这个factory函数包括3个参数

  • require: 一个方法标识符,调用它可以动态的获取一个依赖模块的输出
  • exports: 一个对象,用于对其他模块提供输出接口,例如:exports.name = "xxx"
  • module: 一个对象,存储了当前模块相关的一些属性和方法,其中module.exports属性等同于上面的exports

如下所示

// CMD
define(function (requie, exports, module) {
    //依赖就近书写
    var module1 = require('Module1');
    var result1 = module1.exec();
    module.exports = {
      result1: result1,
    }
});

// AMD
define(['Module1'], function (module1) {
    var result1 = module1.exec();
    return {
      result1: result1,
    }
}); 

AMD和CMD区别:

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同,二者皆为异步加载模块。

// 在 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,
    print: print
  }
})

// 在CMD中math.js
define(function (require, exports, module) {
  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
  }
})  

假如此时m1.js中有一个语句是在m1模块被加载的时候打印出"我是m1, 我被加载了..."

执行结果区别:

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

即 两者之间,最明显的区别就是在模块定义时对依赖的处理不同:

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

 

四 ES6 Modules规范:ES6标准出来后,ES6 Modules规范基本成为了前端的主流

1 export有两种模块导出方式:

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

命名式导出:

 // 以下两种为错误  
// 1.  
export 1;  
// 2.  
const a = 1;  
export a;  
  
// 以下为正确  
// 3.  
const a = 1;  
export { a };  
  
// 4. 接口名与模块内部变量之间,建立了一一对应的关系  
export const a = 1, b = 2;  
  
// 5. 接口名与模块内部变量之间,建立了一一对应的关系  
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 };  

容易混淆的可能是24两种写法了,看着很像,但是2却不行。2直接导出一个值为1的变量是和情况一一样,没有什么意义,因为你在后面要用的时候并不能完成解构。

但是4中,接口名与模块内部变量之间,建立了一一对应的关系,所以可以。

 

默认导出,默认导出会在export后面加上一个default

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

 

2 import导入:import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。

// 某个模块的导出 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'  

第四种写法会获取到module中所有导出的东西,并且赋值到module这个变量下,这样我们就可以用module.a这种方式来引用a了。

 

3 Babel下的ES6模块转换:如果你有使用过一些ES6的Babel的话,你会发现当使用export/import的时候,Babel也会把它转换为exports/require的形式。

 

五 CommonJS与ES6 Module规范的区别:

  • CommonJS模块是运行时加载,ES6 Modules是编译时输出接口
  • CommonJS输出是值的浅拷贝;ES6 Modules输出的是值的引用,被输出模块的内部的改变会影响引用的改变
  • CJS是同步加载,ESM是异步加载;(由于CJS是用于服务器端的模块体系,需要加载的模块都在本地,所以采用同步加载也不会出问题,但是ESM用于浏览器端时,可能涉及到一些异步请求,所以需要采用异步加载。)
  • CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 Modules只能是字符串
  • CommonJSthis指向当前模块,ES6 Modulesthis指向undefined
  • 且ES6 Modules中没有这些顶层变量:argumentsrequiremoduleexports__filename__dirname

 

相关阅读:

NodeJs中引入模块的机制与npm

 

 

 

 

 

 

posted @ 2021-12-28 14:14  TerryMin  阅读(70)  评论(0编辑  收藏  举报