ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

HoRain云--Node.js模块系统

HoRain云--Node.js模块系统 Node.js 模块系统是其核心功能之一它允许开发者将代码组织成小的、可重用的单元这些单元被称为模块。模块是一个封装了特定功能的独立文件可以在其他文件中引入和使用。Node.js 的模块系统是构建 Node.js 应用程序的重要基础使开发者能够将代码拆分成可重用、可维护的小块。每个模块都是一个单独的 JavaScript 文件包含一组相关的函数、对象或变量通过这种方式Node.js 提供了一种结构化的方法来管理大型应用程序使代码更加清晰和易于维护。Node.js 中的每个文件都可以作为一个模块默认处于独立的作用域内。模块系统遵循 CommonJS 规范但也支持 ES 模块。模块类型Node.js 支持以下几种模块内置模块Node.js 自带的模块如fs、http、path等。用户自定义模块由开发者创建的模块。第三方模块通过 npm 安装的模块如express、lodash等。导入内置模块内置模块是由 Node.js 自带的模块在安装 Node.js 时就已经包含在环境中因此无需额外安装。常见的内置模块包括 fs、http、path、os、crypto 等。导入内置模块只需要使用 require() 函数并传入模块名称即可。例如要导入文件系统模块 fs、path、http可以这样做// 导入文件系统模块 const fs require(fs); // 导入路径模块 const path require(path); // 导入 HTTP 模块 const http require(http);这里fs 是一个字符串表示要加载的模块名称。Node.js 会自动识别这是一个内置模块并加载相应的功能。实例const os require(os);console.log(Operating System:, os.platform());console.log(Total Memory:, os.totalmem());导入第三方模块第三方模块是开发者或开源社区发布的模块可以通过 npmNode 包管理器安装到项目中常见的第三方模块有 express、lodash、axios 等。第三方模块安装后这些模块会被放置在项目的 node_modules 目录下。要导入一个第三方模块同样使用 require() 函数但传入的是模块的名称。例如要导入 express 框架可以这样做// 导入第三方模块 express const express require(express);在导入第三方模块之前确保已经通过 npm 安装了该模块。例如安装 express 可以通过以下命令完成npm install express安装第三方模块 lodash并导入测试实例const lodash require(lodash);const array [1, 2, 3, 4];const reversedArray lodash.reverse(array.slice()); // 使用 lodash 反转数组console.log(Reversed Array:, reversedArray);自定义模块导出与导入CommonJS 模块1、导出模块使用 module.exports 或 exports 将函数、对象或变量导出。2、导入模块使用 require() 导入模块。导入模块在 Node.js 中引入一个模块非常简单如下我们创建一个main.js文件并引入 hello 模块代码如下:main.js 文件代码var hello require(./hello);hello.world();以上实例中代码 require(./hello) 引入了当前目录下的hello.js文件./ 为当前目录node.js 文件默认后缀为 .js。Node.js 提供了 exports 和 require 两个对象其中 exports 是模块公开的接口require 用于从外部获取一个模块的接口即所获取模块的 exports 对象。接下来我们就来创建 hello.js 文件代码如下hello.js 文件代码exports.world function() {console.log(Hello World);}在以上示例中hello.js 通过 exports 对象把 world 作为模块的访问接口在 main.js 中通过 require(./hello) 加载这个模块然后就可以直接访问 hello.js 中exports对象的成员函数了。有时候我们只是想把一个对象封装到模块中格式如下module.exports function() { // ... }例如:hello.js 文件代码// hello.jsfunction Hello() {var name;this.setName function(thyName) {name thyName;};this.sayHello function() {console.log(Hello name);};};module.exports Hello;这样就可以直接获得这个对象了main.js 文件代码// main.jsvar Hello require(./hello);hello new Hello();hello.setName(BYVoid);hello.sayHello();模块接口的唯一变化是使用 module.exports Hello 代替了 exports.world function(){}。 在外部引用该模块时其接口对象就是要输出的 Hello 对象本身而不是原先的 exports。module.exports 与 exports 的区别module.exports是导出对象的真正引用。exports是module.exports的快捷方式。不能直接赋值exports ...否则会断开引用。自定义模块导出与导入ES 模块ES 模块使用 import 和 export是现代 JavaScript 的模块规范。ES 模块使用import和export关键字需将文件扩展名设置为.mjs或者在package.json中声明type: module。ES 模块支持静态导入import ... from ...和动态导入import()。实例// myModule.mjsexport function greet(name) {return Hello, ${name}!;}// main.mjsimport { greet } from ./myModule.mjs;console.log(greet(Bob)); // 输出Hello, Bob!加载模块的路径解析1、核心模块如 http 和 fs在 Node.js 安装时就包含可以直接加载。const http require(http);2、本地文件模块使用相对路径或绝对路径加载需指定 ./ 或 /。const myModule require(./myModule);3、第三方模块位于node_modules目录中只需输入模块名称即可加载。const express require(express);模块缓存模块缓存机制Node.js 会将已加载的模块缓存起来以提高性能。再次 require() 同一模块时直接返回缓存中的模块而不是重新加载。刷新缓存要重新加载模块可以删除缓存delete require.cache[require.resolve(./myModule)];循环依赖当两个或多个模块相互导入时称为循环依赖。Node.js 能处理简单的循环依赖但可能导致部分模块导出的对象未完全初始化。实例// a.jsconst b require(./b);console.log(a.js:, b.message);module.exports { message: Hello from a };// b.jsconst a require(./a);console.log(b.js:, a.message);module.exports { message: Hello from b };// main.jsrequire(./a);输出说明 b.js 导入 a.js 时由于 a.js 尚未完全执行a.message 为 undefined。模块包装与作用域Node.js 会将每个模块包装在一个函数中使得每个模块都有独立的作用域。 示例// Node.js 内部将模块包装为类似这样的函数 (function(exports, require, module, __filename, __dirname) { // 模块代码在这里 });这意味着在模块中定义的变量不会污染全局作用域。服务端的模块放在哪里也许你已经注意到我们已经在代码中使用了模块了。像这样var http require(http); ... http.createServer(...);Node.js 中自带了一个叫做http的模块在我们的代码中请求它并把返回值赋给一个本地变量。这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。Node.js 的 require 方法中的文件查找策略如下由于 Node.js 中存在 4 类模块原生模块和3种文件模块尽管 require 方法极其简单但是内部的加载却是十分复杂的其加载优先级也各自不同。如下图所示从文件模块缓存中加载尽管原生模块与文件模块的优先级不同但是都会优先从文件模块的缓存中加载已经存在的模块。从原生模块加载原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后优先检查模块是否在原生模块列表中。以http模块为例尽管在目录下存在一个 http/http.js/http.node/http.json 文件require(http) 都不会从这些文件中加载而是从原生模块中加载。原生模块也有一个缓存区同样也是优先从缓存区加载。如果缓存区没有被加载过则调用原生模块的加载方式进行加载和执行。从文件加载当文件模块缓存中不存在而且不是原生模块的时候Node.js 会解析 require 方法传入的参数并从文件系统中加载实际的文件加载过程中的包装和编译细节在前一节中已经介绍过这里我们将详细描述查找文件模块的过程其中也有一些细节值得知晓。require方法接受以下几种参数的传递http、fs、path等原生模块。./mod或../mod相对路径的文件模块。/pathtomodule/mod绝对路径的文件模块。mod非原生模块的文件模块。在路径 Y 下执行 require(X) 语句执行顺序1. 如果 X 是内置模块 a. 返回内置模块 b. 停止执行 2. 如果 X 以 / 开头 a. 设置 Y 为文件根路径 3. 如果 X 以 ./ 或 / or ../ 开头 a. LOAD_AS_FILE(Y X) b. LOAD_AS_DIRECTORY(Y X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. 抛出异常 not found LOAD_AS_FILE(X) 1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。 2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。 3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。 4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。 LOAD_INDEX(X) 1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。 2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。 3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。 LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 main 字段。 b. let M X (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRSNODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS path split(START) 2. let I count of PARTS - 1 3. let DIRS [] 4. while I 0, a. if PARTS[I] node_modules CONTINUE b. DIR path join(PARTS[0 .. I] node_modules) c. DIRS DIRS DIR d. let I I - 1 5. return DIRSexports 和 module.exports 的使用如果要对外暴露属性或方法就用exports就行要暴露对象(类似class包含了很多属性和方法)就用module.exports。
返回列表