Skip to content

模块化

模块化介绍

Node 应用由模块(每一个JS即是一个模块)组成,采用CommonJS模块规范(提供了模块引入导出的规则)。每个文件就是一个模块,有自己的作用。

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,对于整个系统来说,模块是可组合,分解和更换的单元。

模块化特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照加载模块的代码的书写顺序。

模块化的好处

  • 提高代码的复用性
  • 提高代码的可维护性
  • 可以实现按需加载

模块化规范

①CommonJS 规范

CommonJS 是一种模块化规范,最初提出来是在浏览器以外的地方使用,并且当时命名为 ServerJS,后来为了体现它的广泛性,更名为 CommonJS,也可以简称为 CJS Node 是 CommonJS 在服务端一个具有代表性的实现 Browserify 是 CommonJS 在浏览器端的一种实现 webpack 具备对 CommonJS 的支持与转换

②AMD 规范

AMD 主要是应用于浏览器的一种模块化规范,AMD 是 Asynchronous Module Definition(异步模块定义)的缩写,它采用的是异步加载模块,事实上 AMD 的规范早于 CommonJS,但是现在 CommonJS 仍被使用,但 AMD 已经很少用了。 实现 AMD 规范的库主要是 require.js 和 curl.js

③CMD 规范

CMD 也是应用于浏览器的一种模块化规范,CMD 是 Common Module Definition(通用模块定义)的缩写,他也是采用了异步加载模块,但是它将 CommonJS 的优点吸收了过来,这个目前也很少使用了。 SeaJS 实现了 CMD 规范

④ ES Module 规范

ES Module 规范是 ES 提出的,是官方的模块化规范。

Node 中 模块的分类

Node.js中根据模块来源的不同,将模块分为了3大类,分别是:

  • 内置模块(由Node.js官方提供,例如:fs,path,http)
  • 自定义模块:用户创建的每个JS文件,都是自定义模块。,
  • 第三方模块:由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载。

Node.js 支持CommonJS 规范和ES6 模块规范

CommonJS 模块规范

在模块中暴露数据

1)模块内如果没有暴露数据,引人模块的时候会得到一个空对象。

2)通过给 module.exports 赋值,实现暴露数据。

3)通过为 module.exports 设置属性,暴露数据。

4)通过为 exports 设置属性,暴露数据。 给 exports 设置属性,就是给 module.exports 设置属性,但不能改变exports的引用地址,这样的话 exportsmodule.exports 就脱钩了。

js
// 修改 exports 的值, exports 与原来的引用就断了,exports 就无法暴露数据了
// exports = [10,20,30,40,50];

引入模块(自定义模块)

1)自定义模块的地址需要以 ./../ 开头,这是模块文件的相对路径,相对于当前的执行的 JS 脚本的位置,不是命令行打开的目录。

2)如果模块文件的地址没有以 ./../ 开头,会被认为是内置模块或第三方模块的模块名。

3)如果模块文件扩展名是 .js 或者是 .json ,在导入的时候可以省略扩展名。如果引入模块文件时,模块路径没有扩展名,会依次查找 .js 文件、.json 文件、目录。

模块文件的扩展名

对于不同扩展名的模块文件,Node.js 有不同的处理方式

  • 扩展名是 .js的模块文件: 读取文件内容并编译执行并获取模块中暴露的数据。
  • 扩展名是.json的模块文件: 读取文件,用 JSON.parse() 解析返回结果作为获取的数据。
  • 扩展名是.node的模块文件: 这是 c/c++ 编写的扩展文件,通过 dlopen() 方法编译。
  • 其他扩展名,文件内容会被当做 JavaScript 代码去解析。

整个目录作为一个模块

1)会默认加载该目录下 package.json 文件中 main 属性定义的入口文件。

2)如果没有package.json, 或者 main 属性对应的文件不存在,则自动找 index.jsindex.json 作为入口文件。

js
// 导入模块
// 1. 模块中没有暴露数据,require() 返回空对象;  require()多次,但是模块只有第一次导入,模块代码执行一遍,后面都是换成
require('./modules/01-mod.js');
require('./modules/01-mod.js');
require('./modules/01-mod.js');
const mod01 = require('./modules/01-mod.js');
console.log(mod01);


-------------------------------
// 通过 给 moudle.exports 赋值暴露数据
const obj = {
    name: '小乐',
    age: 19,
    say() {
        console.log(this.name);
    }
}
// 后面的覆盖前面的
module.exports = 100;
module.exports = [10,20,30,40,50,60];
module.exports = obj;
// 2. 模块中可以通过给 module.exports 赋值实现暴露数据
const mod02 = require('./modules/02-mod.js');
console.log(mod02);


--------------------------------------
// 3. 如果模块暴露的数据是个对象 导入模块的时候可以使用解构赋值
const {name,age,say} = require('./modules/02-mod');
console.log(age);
say();

------------------------------------
// 模块中可以 给 module.exports 添加属性,可以暴露多个数据
// 给 module.exports 添加属性
module.exports.getMessage = () => {
    console.log('mod03 get Message')
};

module.exports.setMessage = () => {
    console.log('mod03 set messsage')
}
// 4.导入模块会得到一个对象,暴露的多个数据都是对象的属性
const mod03 = require('./modules/03-mod');
console.log(mod03);
mod03.getMessage();

------------------------------------------
// 模块中可以 给 exports 添加属性,可以暴露多个数据; 但是不能给 exports 重新赋值
const mod04 = require('./modules/04-mod');
console.log(mod04);
-----------------------------------------
// 导入 json 得到根据json数据解析出来的对象
const mod05 = require('./modules/05-mod');
console.log(mod05);
console.log('');

// 导入一个扩展名不是 .js、.json、.node 的模块文件
const mod06 = require('./modules/06-mod.sb');
console.log(mod06);
console.log('');
---------------------------------------
// 整个目录作为模块  在目录中的 pageage.son 中设置入口文件
    
{
    "main": "01.js"
}

const mod07 = require('./modules/07-mod');
console.log(mod07);

// 整个目录作为模块,目录中没有配置入口文件,默认找 index.js 作为入口文件

const mod08 = require('./modules/08-mod');
console.log(mod08);

ES6 模块规范

Node 中使用 ES 模块规范

Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export关键字,那么就必须采用.mjs后缀名。

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。package是node 的配置文件

js
{
    "type": "module"
}

在模块中暴露数据

① 暴露单个数据

使用 export default 可以在模块中暴露单个数据,注意文件中 export default 语句只能出现一次。

js
const usre = {
    name: '小乐',
    age: 19,
    address: '上海',
    say() {
        console.log(this.name + '住在' + this.address);
    }
}

// 暴露一个数据  export default 语法只能写一遍
// export default 100;
// export default [10,20,30,40,50];
export default usre;

② 暴露多个数据

使用 export 可以暴露多个数据,有两种写法:

js
// 第一种写法 在声明变量的同时暴露
export const firstName = 'Lee';
export const lastName = 'KeQiang';
export const year = 1918;
export function fn() {};
export const obj = {name:'mingge',age:100}


// 第二种写法 在文件底部统一暴露(推荐)
const firstName = 'Lee';
const lastName = 'KeQiang';
const year = 1918;
function fn() {};
const obj = {name:'mingge',age:100}

export {firstName, lastName, year, fn, obj}

引入模块并使用模块中暴露的数据

① 模块使用 export default 暴露单个数据

js
import 变量名 from '模块地址';

② 模块使用 export 暴露多个数据

js
// 获取的变量名必须与模块暴露的变量名一致,可以多次分别获取,可以取别名
import {name, year as y} from '模块地址';
import {fn} from '模块地址';

// 可以将模块中的数据整体加载
import * as 别名 from '模块地址';

Node导入ES6模块语法,需要将文件的拓展名更改为mjs。

③导入内部模块

js
// 使用ES6模块的语法 导入内部模块
import * as fs from 'node:fs';
fs.writeFile('data1.txt', 'hello world', err=>{});

案例

js
// 2 导入模块 模块中使用 export 关键字暴露多个数据
import {user, data as datas} from './modules/02-mod.js';
import {getMessage} from './modules/02-mod.js';
console.log(user);
console.log(datas);
console.log(getMessage);
// 将模块中暴露的所有数据导出到一个对象
import * as mod02 from './modules/02-mod.js';
console.log(mod02);

模块中的相对路径问题

js
文件的路径: 使用相对路径,参照命令行所在的目录
模块的路径: 使用相对路径,参照所在文件的目录,跟命令行所在的目录