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

相关推荐
垣宇14 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
爱吃南瓜的北瓜15 小时前
npm install 卡在“sill idealTree buildDeps“
前端·npm·node.js
翻滚吧键盘15 小时前
npm使用了代理,但是代理软件已经关闭导致创建失败
前端·npm·node.js
浪九天16 小时前
node.js的版本管理
node.js
浪九天18 小时前
node.js的常用指令
node.js
浪九天20 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
小纯洁w1 天前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
熬夜不洗澡1 天前
Node.js中不支持require和import两种导入模块的混用
node.js
bubusa~>_<1 天前
解决npm install 出现error,比如:ERR_SSL_CIPHER_OPERATION_FAILED
前端·npm·node.js
天下皆白_唯我独黑1 天前
npm 安装扩展遇到证书失效解决方案
前端·npm·node.js