模块化
模块化介绍
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
的引用地址,这样的话 exports
与 module.exports
就脱钩了。
// 修改 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.js
、 index.json
作为入口文件。
// 导入模块
// 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 的配置文件
{
"type": "module"
}
在模块中暴露数据
① 暴露单个数据
使用 export default
可以在模块中暴露单个数据,注意文件中 export default
语句只能出现一次。
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
可以暴露多个数据,有两种写法:
// 第一种写法 在声明变量的同时暴露
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
暴露单个数据
import 变量名 from '模块地址';
② 模块使用 export
暴露多个数据
// 获取的变量名必须与模块暴露的变量名一致,可以多次分别获取,可以取别名
import {name, year as y} from '模块地址';
import {fn} from '模块地址';
// 可以将模块中的数据整体加载
import * as 别名 from '模块地址';
Node导入ES6模块语法,需要将文件的拓展名更改为mjs。
③导入内部模块
// 使用ES6模块的语法 导入内部模块
import * as fs from 'node:fs';
fs.writeFile('data1.txt', 'hello world', err=>{});
案例
// 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);
模块中的相对路径问题
文件的路径: 使用相对路径,参照命令行所在的目录
模块的路径: 使用相对路径,参照所在文件的目录,跟命令行所在的目录