Node.js模块化

Node.js 模块化

介绍

模块化与模块

将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为模块化。其中拆分的每一个文件就是模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用。

  • 在 Node.js 中,文件就是模块,每个.js.json文件都可以被看作是一个独立的模块。
  • Node.js 会缓存加载过的模块,这意味着如果同一个模块被多次请求,它只会在第一次加载时执行,后续请求将直接从缓存中读取。
  • 每个模块都有自己的局部变量和函数作用域,这有助于防止全局命名空间的污染。
导入与导出
  • 导出:使用 module.exports 对象来暴露模块中的函数、对象或值。例如:

    // myModule.js
    function sayHello() {
    console.log('Hello!');
    }

    module.exports = {
    sayHello: sayHello
    };
    // or
    exports.sayHello = sayHello;

  • module.export可以暴露任意数据
  • 不能使用exports = value的形式暴露数据(比如exports = '1',打印的结果会是空对象{}),模块内部module与exports的隐式关系 exports = module.exports = {}

举例说明:

复制代码
// a.js
const b = require('./b');
console.log(b) // 输出{}
// b.js
exports= '1';

console.log(module.exports) // 输出{}
console.log(exports === module.exports) // 输出true

repuire返回的结果是目标模块的module.exports的值,并不是exports的值,因为require机制设计的本质就是返回模块的导出值,而这个导出值正是由module.exports确定的。

举例说明

复制代码
c.js
exports.greeting = 'Hello,would';
// 等于
exports.module.greeting = 'Hello,would';

当你在另一个文件中使用require加载c.js时:

复制代码
const c = require(./c);
console.log(c.greeting) //  输出 "Hello, world!"

上面的例子中,c变量将指向c.js文件中module.exports对象的内容。

可以像图示这样理解:

然而,需要注意的是,如果在模块中直接将module.exportsexports重新赋值给一个新的对象或值,这将切断它们之间的默认关联。例如:

复制代码
// myModule.js
module.exports = { greeting: "Hello, world!" };
// 或者
exports = { greeting: "Hello, world!" };

在上述任何一种情况下,如果使用exports进行重新赋值,则module.exports将不再被更新,反之亦然。因此,在实际开发中,通常推荐直接操作module.exports以避免意外的副作用。这样可以确保无论何时使用require,都会获得准确的模块导出值

  • 导入:使用 require()函数来引入其他模块。例如:

    // app.js
    const myModule = require('./myModule');

    myModule.sayHello();

  • 默认导出单个实体

    // myModule.js
    export default function sayHello() {
    console.log('Hello!');
    }

  • 导入

    // app.js
    import sayHello from './myModule';
    sayHello();

Node.js的导入/导出语法默认遵循CommonJS规范 ,对于ES6的import/export语法的支持是在Node.js v14.5.0 及更高版本中添加的,并且需要在文件扩展名或文件顶部指定type="module"

require使用的一些注意事项:

  1. 自己创建的模块,导入路径时建议写相对路径,且不能省略./../
  2. js和json文件导入时可以不用写后缀,如果出现文件名相同,则js优先
  3. 如果导入其他类型的文件,会以js文件进行处理,如果导入的路径是个文件夹,则会先去检测该文件夹下package.json文件中的main属性对应的文件,存在则导入,反之则报错,如果main属性不存在,或者package.json不存在,则会尝试导入文件夹在的index.js和index.json,如果找不到,则报错。

文件树:

复制代码
│  main.js
│  
└─modules
        app.js
        package.json

// mian.js
const greeting = require('./modules');
console.log(greeting); // 打印 hello

// app.js
module.exports = 'hello';

// pageage.json
{
	"main": "./app.js"
}
  1. 导入 node.js 内置模块时,直接require模块名即可,无需.././

导入模块的基本流程

介绍require导入自定义模块的基本流程

复制代码
// 新建greeting.js
module.exports = 'Hello World!';
  1. 将相对路径转为绝对路径,定位目标文件

    // 引入模块
    const greeting = require('./greeting.js');

    // 伪代码
    function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);
    }

    // 引入模块
    const greeting = require('./greeting.js');

  2. 缓存检测

    function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);

    复制代码
     // 2. 缓存检测
     if(caches[absolutePath]) {
         return caches[absolutePath];
     }

    }

    const greeting = require('./greeting.js');

  3. 读取目标文件代码

    function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);

    复制代码
     // 2. 缓存检测
     if(caches[absolutePath]) {
         return caches[absolutePath];
     };
     
     // 3. 读取目标文件代码 readFileAsync返回的是buffer,用toString转换一下
     let code = fs.readFileAsync(absolutePath).toString();

    }

    const greeting = require('./greeting.js');

  4. 包裹为一个函数并执行(自执行函数)。通过arguments.callee.toString()查看自执行函数

    // 修改greeting.js
    const greeting = {
    info: 'Hello World!'
    }

    module.exports = greeting;

    // 输出 arguments.callee指向函数 toString可以看到函数的代码体
    console.log(arguments.callee.toString());

复制代码
function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);

    // 2. 缓存检测
    if (caches[absolutePath]) {
        return caches[absolutePath];
    };

    // 3. 读取目标文件代码
    let code = fs.readFileAsync(absolutePath).toString();

    // 4. 包裹为一个函数并执行(自执行函数)。通过`arguments.callee.toString()`查看自执行函数
    const module = {};  // 实参1
    const exports = {}; // 实参2
    (function (exports, require, module, __filename, __dirname) {
        const greeting = {
            info: 'Hello World!'
        }

        module.exports = greeting;

        // 输出
        console.log(arguments.callee.toString());
    })(exports, require, module, __filename, __dirname); // 修改为自执行函数 需要传入实参1,2
}

const greeting = require('./greeting.js');
  1. 缓存模块的值

    function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);

    复制代码
     // 2. 缓存检测
     if (caches[absolutePath]) {
         return caches[absolutePath];
     };
    
     // 3. 读取目标文件代码
     let code = fs.readFileAsync(absolutePath).toString();
    
     // 4. 包裹为一个函数并执行(自执行函数)。通过`arguments.callee.toString()`查看自执行函数
     const module = {};  // 实参1
     const exports = {}; // 实参2
     (function (exports, require, module, __filename, __dirname) {
         const greeting = {
             info: 'Hello World!'
         }
    
         module.exports = greeting;
    
         // 输出
         console.log(arguments.callee.toString());
     })(exports, require, module, __filename, __dirname); // 修改为自执行函数 需要传入实参1,2
    
     	// 5. 缓存结果值
    		 caches[absolutePath] = module.exports;

    }

    const greeting = require('./greeting.js');

  2. 返回module.exports的值

    function require(file) {
    // 1. 将相对路径转为绝对路径
    const absolutePath = path.resolve(__dirname, file);

    复制代码
     // 2. 缓存检测
     if (caches[absolutePath]) {
         return caches[absolutePath];
     };
    
     // 3. 读取目标文件代码
     let code = fs.readFileAsync(absolutePath).toString();
    
     // 4. 包裹为一个函数并执行(自执行函数)。通过`arguments.callee.toString()`查看自执行函数
     const module = {};  // 实参1
     const exports = {}; // 实参2
     (function (exports, require, module, __filename, __dirname) {
         const greeting = {
             info: 'Hello World!'
         }
    
         module.exports = greeting;
    
         // 输出
         console.log(arguments.callee.toString());
     })(exports, require, module, __filename, __dirname); // 修改为自执行函数 需要传入实参1,2
    
     	// 5. 缓存结果值
    		 caches[absolutePath] = module.exports;
    
     	// 6. 返回
     	retrun module.exports;

    }

    const greeting = require('./greeting.js');

模块化项目

编码时是按照一个一个编码,整个项目就是一个模块化的项目

模块路径
  • 相对路径: 如果你引用的是项目内的模块,可以使用相对路径。
  • 绝对路径: 引用系统模块或安装的第三方模块时,使用绝对路径或模块名称即可。
模块解析

当 Node.js 尝试解析模块路径时,它会遵循以下步骤:

  1. 检查是否为内置模块。
  2. 尝试在当前目录下查找模块。
  3. 检查 node_modules 目录。
  4. 查找指定的.js, .json, 或.node 文件。

模块化优点

  1. 防止命名冲突
  2. 高复用性
  3. 高维护性

CommonJS规范

module.exportsexports以及require这些都属于CommonJS模块化规范中的内容,而Node.js是实现了CommonJS模块化规范,二者关系有点像JavaScriptECMASriprt的关系。

相关推荐
神仙别闹4 小时前
基于VUE+Node.JS实现(Web)学生组队网站
前端·vue.js·node.js
BXCQ_xuan5 小时前
基于Node.js的健身会员管理系统的后端开发实践
后端·mysql·node.js
wt_cs5 小时前
身份证实名认证接口数字时代的信任基石-node.js实名认证集成
开发语言·node.js·php
李剑一8 小时前
写一个vitepress新建文章脚本,自动化创建链接,别再手写了!
前端·node.js·vitepress
名字越长技术越强1 天前
Node.js学习
学习·node.js
知识分享小能手1 天前
JavaScript学习教程,从入门到精通,Ajax与Node.js Web服务器开发全面指南(24)
开发语言·前端·javascript·学习·ajax·node.js·html5
dwqqw1 天前
opencv图像库编程
前端·webpack·node.js
layman05281 天前
node.js 实战——(fs模块 知识点学习)
javascript·node.js
本本啊1 天前
node 启动本地应用程序并设置窗口大小和屏幕显示位置
前端·node.js
全栈派森1 天前
Next15 + Prisma + Auth5 实战讲解
react.js·node.js·next.js