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

相关推荐
爱编程的鱼3 小时前
Node.js事件循环:解锁异步编程的奥秘
node.js
南暮思鸢3 小时前
Node.js is Web Scale
经验分享·web安全·网络安全·node.js·ctf题目·hackergame 2024
程序员小杰@3 小时前
Playwright 快速入门:Playwright 是一个用于浏览器自动化测试的 Node.js 库
node.js
Martin -Tang4 小时前
vite和webpack的区别
前端·webpack·node.js·vite
王解4 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
ldq_sd14 小时前
node.js安装和配置教程
node.js
我真的很困15 小时前
坤坤带你学浏览器缓存
前端·http·node.js
whyfail18 小时前
ESM 与 CommonJS:JavaScript 模块化的两大主流方式
javascript·node.js
熊的猫19 小时前
ES6 中 Map 和 Set
前端·javascript·vue.js·chrome·webpack·node.js·es6
Pigwantofly21 小时前
软件工程概论项目(二),node.js的配置,npm的使用与vue的安装
node.js