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的关系。

相关推荐
理想不理想v23 分钟前
webpack最基础的配置
前端·webpack·node.js
南城巷陌3 小时前
JWT认证机制在Node.js中的详细阐述
node.js·jwt认证机制·前端安全认证
理想不理想v5 小时前
node.js的简单示例
node.js
yrldjsbk5 小时前
使用Node.js搭配express框架快速构建后端业务接口模块Demo
node.js·express
维李设论5 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
CodeChampion7 小时前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
Domain-zhuo7 小时前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
理想不理想v7 小时前
webpack如何自定义插件?示例
前端·webpack·node.js
斜杠poven11 小时前
为什么加try catch 不会 block 进程?
前端·javascript·node.js
韩俊强1 天前
使用Docker部署一个Node.js项目
docker·容器·node.js