Node.js模块化module
大约 9 分钟
Node.js模块化module
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
学习目的
模块化module
模块化module介绍
背景:
- 单文件服务适用于应对简单问题,代码量少的场景
- 复杂场景,代码量大场景时,会有:
- 变量命名问题
- 复用性差,函数封装不能复用
- 可维护性差,不适用多人开发
定义:
- 将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为
模块化
,其中拆分出的每个文件就是一个模块
- 模块的内部数据是私有的,不过模块可以暴露内部数据,以便其他模块使用、复用
- 各个模块,依据引用规范,可以整合到一起
- 将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为
项目的模块化/具有模块化特征的项目
- 编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
模块化好处:
- 防止命名冲突(准确说是:减少命名冲突的概率,模块之间数据是私有的)
- 高复用性
- 高维护性(团队成员按模块开发、维护、升级)
体验模块化
- 创建 me.js 文件/模块
//声明函数 function tiemo() { console.log('贴膜....'); } //暴露数据,将函数赋值给属性 module.exports = tiemo;
- 创建 index.js ,引入me.js 文件/模块
//导入模块,require可以导入内置模块和自定义外部模块 const tiemo = require('./me.js'); // require的返回结果就是目标模块中module.exports的值 //调用函数 tiemo();
模块向外暴露数据
模块暴露数据的方式有两种:
- module.exports = value
- exports.name = value
使用时有几点注意:
- module.exports 可以暴露
任意
数据;
- 本质:对外保留module中的exports属性,多个属性是一个对象;即:module.exports =
- 不能使用
exports = value
的形式暴露数据,因为模块内部 module 与 exports 存在隐式关系exports = module.exports = {}
exports === module.exports === 一个空对象
- 本质:exports.name,此处name是一个独立的变量,相当于往exports这个对象中添加属性;即:exports.属性名 = 属性值
require 返回的是目标模块中
module.exports
的值
- 创建 me.js 文件/模块,通过 module.exports =
//声明一个函数
function tiemo(){
console.log('贴膜...');
}
//捏脚
function niejiao(){
console.log('捏脚....');
}
// 暴露数据
module.exports = {
tiemo,
niejiao
}
// 1. module.exports 可以暴露`任意`数据
module.exports = 'iloveyou';
module.exports = 521;
- 创建 index.js ,引入me.js 文件/模块
//导入模块
const me = require('./me.js');
// 打印输出结果为两个函数
console.log(me);
// 函数调用
me.tiemo();
me.niejiao();
- 创建 me.js 文件/模块,通过 exports.属性名 暴露数据
//声明一个函数
function tiemo(){
console.log('贴膜...');
}
//捏脚
function niejiao(){
console.log('捏脚....');
}
// 暴露数据,相当于往对象添加属性
exports.niejiao = niejiao;
exports.tiemo = tiemo;
//2. 不能使用 `exports = value`的形式暴露数据
// exports = 'iloveyou' // X,打印返回的结果是个空对象
// 本质:
// exports = module.exports = {}
console.log(module.exports); // 打印结果为空对象
console.log(module.exports === exports); // 打印结果为true
- 创建 index.js ,引入me.js 文件/模块
//导入模块
const me = require('./me.js');
// require的返回结果是目标模块中module.exports的值,并不是exports的值
// 打印输出结果,同样为两个函数,导出的作用等价
console.log(me);
// 函数调用
me.tiemo();
me.niejiao();
导入(引入)模块/文件夹
在模块中使用 require 传入文件路径即可引入文件
const test = require('./me.js');
require 使用的一些注意事项:
- 对于自己创建的模块,导入时路径建议写
相对路径
,且不能省略./
和../
js
和json
文件导入时可以不用写后缀(优先查找js文件),c/c++编写的node
扩展文件也可以不写后缀,但是一般用不到- 导入时省略后缀的话,遇到同名但后缀不同的文件时,会优先导入
.js
的文件,没有时再去导入.json
- 导入时省略后缀的话,遇到同名但后缀不同的文件时,会优先导入
- 如果导入其他类型的文件,会以
js
文件进行处理 - 如果导入的路径是个文件夹,则会
首先
检测该文件夹下package.json
文件中main
属性对应路径中的文件,- 如果存在则导入,反之,如果文件不存在会报错。
- 如果
main
属性不存在,或者package.json
不存在,则会尝试导入文件夹下的index.js
和index.json
,如果还是没找到,就会报错 - 包管理工具,本质上就是导入的文件夹
- 导入 node.js 内置模块时,直接 require 模块的名字即可,无需加
./
和../
引入模块举例
//导入模块
// fs 使用时推荐写绝对路径 ;require引入,推荐写相对路径,不受工作目录影响
const tiemo = require('./me.js');
//省略后缀 JS
const tiemo = require('./me');
//导入 JSON 文件,也可以省略后缀
const duanzi = require('./duanzi');
// 等价于
const duanzi = require('./duanzi.json');
// 打印导入的json文件,值是一个对象,可直接访问其中属性,不是字符串
console.log(duanzi);
// 导入时省略后缀的话,遇到同名但后缀不同的文件时,会优先导入.js的文件
//导入其他类型的文件,会以 `js` 文件进行处理
const test = require('./test.abc')
const test = require('./test')
console.log(test)
//调用函数
tiemo();
require导入模块的基本流程
require
导入自定义模块
的基本流程
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)
- 可通过
arguments.callee.toString()
查看自执行函数
- 可通过
- 缓存模块的值
- 返回
module.exports
的值
- 举例require导入的文件是怎么被执行的,理解导入模块的基本流程
- 可以理解为读取伪代码,让代码自运行生成对象,再使用这个对象,因为导入文件最终导入的是对象
- 可以理解为:io流读取代码-->解析代码-->立即执行代码-->找出该模块暴露的东西赋值给本地变量-->缓存结果-->return返回 module.exports 的值
/**
* 伪代码逻辑展示
*/
function require (file) {
//1. 将相对路径转为绝对路径,定位目标文件
let absolutePath = path.resolve(__dirname, file)
//2. 缓存检测,如果此前加载过/导入过文件/模块记录,直接将暴露值返回,如果没有则下一步
if (caches[absolutePath]) {
return caches[absolutePath]
}
//3. 读取文件的代码
let code = fs.readFileSync(absolutePath).toString()
//4. 包裹为一个函数 然后执行
let module = {}
let exports = module.exports = {};
// (function(形参){函数逻辑})(传参)是一个立刻执行函数
(function (exports, require, module, __filename, __dirname) {
const test = {
name: '尚硅谷'
}
module.exports = test
// 输出本文件代码
// 本文件的代码,在内部执行时,是包裹在一个函数中执行的,arguments.callee指向本函数,toString()函数代码体转字符串
console.log(arguments.callee.toString())
// 函数执行,并调入实参,这些变量在require内部都是有声明的
})(exports, require, module, __filename, __dirname)
//5. 缓存结果,nodejs缓存的是模块对象
caches[absolutePath] = module.exports
//6. 返回 module.exports 的值
return module.exports
}
// 调用
const m = require('./me.js');
GPT (function(形参){函数逻辑})(传参)是一个立刻执行函数吗
是的,(function(形参){函数逻辑})(传参) 是一个立刻执行函数 (IIFE)。
IIFE 是一种立即执行的匿名函数表达式,它通过将函数表达式括在圆括号中,然后紧接着调用该函数来实现。IIFE 的语法如下:
Copy
(function(形参){
// 函数逻辑
})(传参);
IIFE 的特点是:
**立即执行:**IIFE 在定义时就会立即执行,而不会等到被调用。
**私有作用域:**IIFE 创建了一个新的作用域,该作用域内的变量和函数对外部代码不可见。
**模块化:**IIFE 可以用来封装代码,防止污染全局作用域。
IIFE 的一个常见用途是用来避免命名冲突。例如,如果你有一个与全局变量同名的局部变量,可以使用 IIFE 来创建一个新的作用域,在这个作用域内使用局部变量,而不会影响全局变量。
示例:
Copy
// 避免命名冲突
var globalVar = "全局变量";
(function(){
var localVar = "局部变量";
console.log(localVar); // 输出:局部变量
console.log(globalVar); // 输出:全局变量
})();
console.log(localVar); // 报错:localVar 未定义
console.log(globalVar); // 输出:全局变量
在上面的示例中,IIFE 创建了一个新的作用域,localVar 变量在这个作用域内定义,因此不会影响全局变量 globalVar。
五、CommonJS 规范
module.exports
、exports
以及require
这些都是CommonJS
模块化规范中的内容。- CommonJS本身就是一个规范、一个标准
- 而
Node.js
是实现了CommonJS
模块化规范,Node.js与CommonJS的二者关系,有点像JavaScript
与ECMAScript
- 可以理解为,CommonJS是一个模块化规范,Node.js 是对其的一个实现
- ECMAScript是一个规范、一个标准,JavaScript是对其的一个实现