Node.js API详解之 module模块用法实例分析

本文实例讲述了Node.js API详解之 module模块用法。分享给大家供大家参考,具体如下:

Node.js API详解之 module

Node.js 有一个简单的模块加载系统。

在 Node.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。

例子,假设有一个名为 foo.js 的文件:

const circle = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`);

在第一行中,foo.js 加载了同一目录下的 circle.js 模块。

circle.js 文件的内容为:

const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;

circle.js 模块导出了 area() 和 circumference() 两个函数。

通过在特殊的 exports 对象上指定额外的属性,函数和对象可以被添加到模块的根部。

模块内的本地变量是私有的,因为模块被 Node.js 包装在一个函数中(详见模块包装器)。

在这个例子中,变量 PI 是 circle.js 私有的。

module.exports 属性可以被赋予一个新的值(例如函数或对象)。

如下,bar.js 会用到 square 模块,square 导出一个构造函数:

const square = require('./square.js');
const mySquare = square(2);
console.log(`正方形的面积是 ${mySquare.area()}`);

square 模块定义在 square.js 中:

// 赋值给 `exports` 不会修改模块,必须使用 `module.exports`
module.exports = (width) => {
 return {
  area: () => width ** 2
 };
};

模块系统在 require(‘module') 模块中实现。

模块包装器

说明:

在执行模块代码之前,Node.js 会使用一个如下的函数包装器将其包装:

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

通过这样做,Node.js 实现了以下几点:
它保持了顶层的变量(用 var、const 或 let 定义)作用在模块范围内,而不是全局对象。
它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
实现者可以用于从模块中导出值的 module 和 exports 对象。
包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。

文件模块

说明:

如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、.json 或 .node 拓展名再加载。
.js 文件会被解析为 JavaScript 文本文件,
.json 文件会被解析为 JSON 文本文件。
.node 文件会被解析为通过 dlopen 加载的编译后的插件模块。
以 ‘/' 为前缀的模块是文件的绝对路径。 例如,require(‘/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。
以 ‘./' 为前缀的模块是相对于调用 require() 的文件的。 也就是说,circle.js 必须和 foo.js 在同一目录下以便于 require(‘./circle') 找到它。
当没有以 ‘/'、'./' 或 ‘../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
如果给定的路径不存在,则 require() 会抛出一个 code 属性为 ‘MODULE_NOT_FOUND' 的 Error。

目录作为模块

说明:

可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。
把目录递给 require() 作为一个参数,有三种方式。
第一种方式是在根目录下创建一个 package.json 文件,并指定一个 main 模块。

例子,package.json 文件类似:

{ "name" : "some-library",
 "main" : "./lib/some-library.js" }

如果这是在 ./some-library 目录中,则 require(‘./some-library') 会试图加载 ./some-library/lib/some-library.js。
这就是 Node.js 处理 package.json 文件的方式。
注意:如果 package.json 中 “main” 入口指定的文件不存在,则无法解析,Node.js 会将模块视为不存在,并抛出默认错误:

Error: Cannot find module 'some-library'

如果目录里没有 package.json 文件,则 Node.js 就会试图加载目录下的 index.js 或 index.node 文件。
例如,如果上面的例子中没有 package.json 文件,则 require(‘./some-library') 会试图加载:
./some-library/index.js
./some-library/index.node

模块加载顺序

说明:

如果传递给 require() 的模块标识符不是一个核心模块,也没有以 ‘/' 、 ‘../' 或 ‘./' 开头,
则 Node.js 会从当前模块的父目录开始,尝试从它的 /node_modules 目录里加载模块。
Node.js 不会附加 node_modules 到一个已经以 node_modules 结尾的路径上。
如果还是没有找到,则移动到再上一层父目录,直到文件系统的根目录。
例子,如果在 ‘/home/ry/projects/foo.js' 文件里调用了 require(‘bar.js'),则 Node.js 会按以下顺序查找:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
通过在模块名后包含一个路径后缀,可以请求特定的文件或分布式的子模块。
例如,require(‘example-module/path/to/file') 会把 path/to/file 解析成相对于 example-module 的位置。
后缀路径同样遵循模块的解析语法。

从全局目录加载

说明:

如果 NODE_PATH 环境变量被设为一个以冒号分割的绝对路径列表,则当在其他地方找不到模块时 Node.js 会搜索这些路径。
注意:在 Windows 系统中,NODE_PATH 是以分号间隔的。
在当前的模块解析算法运行之前,NODE_PATH 最初是创建来支持从不同路径加载模块的。
虽然 NODE_PATH 仍然被支持,但现在不太需要,因为 Node.js 生态系统已制定了一套存放依赖模块的约定。
有时当人们没意识到 NODE_PATH 必须被设置时,依赖 NODE_PATH 的部署会出现意料之外的行为。
有时一个模块的依赖会改变,导致在搜索 NODE_PATH 时加载了不同的版本(甚至不同的模块)。
此外,Node.js 还会搜索以下位置:
1: $HOME/.node_modules
2: $HOME/.node_libraries
3: $PREFIX/lib/node
其中 $HOME 是用户的主目录,$PREFIX 是 Node.js 里配置的 node_prefix。
这些主要是历史原因。
注意:强烈建议将所有的依赖放在本地的 node_modules 目录。 这样将会更快地加载,且更可靠。

缓存

说明:

模块在第一次加载后会被缓存。 这也意味着(类似其他缓存机制)如果每次调用 require(‘foo') 都解析到同一文件,则返回相同的对象。
多次调用 require(foo) 不会导致模块的代码被执行多次。 这是一个重要的特性。
借助它, 可以返回“部分完成”的对象,从而允许加载依赖的依赖, 即使它们会导致循环依赖。
如果想要多次执行一个模块,可以导出一个函数,然后调用该函数。
注意:
模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从 node_modules 目录加载),
这样就不能保证 require(‘foo') 总能返回完全相同的对象。
此外,在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。
例如,require(‘./foo') 和 require(‘./FOO') 返回两个不同的对象,而不会管 ./foo 和 ./FOO 是否是相同的文件。

循环

说明:

当循环调用 require() 时,一个模块可能在未完成执行时被返回。

例如以下情况:
a.js:

console.log('a 开始');
 exports.done = false;
 const b = require('./b.js');
 console.log('在 a 中,b.done = %j', b.done);
 exports.done = true;
 console.log('a 结束');

b.js:

console.log('b 开始');
 exports.done = false;
 const a = require('./a.js');
 console.log('在 b 中,a.done = %j', a.done);
 exports.done = true;
 console.log('b 结束');

main.js:

console.log('main 开始');
 const a = require('./a.js');
 const b = require('./b.js');
 console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);

当 main.js 加载 a.js 时,a.js 又加载 b.js。
此时,b.js 会尝试去加载 a.js。
为了防止无限的循环,会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块。
然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。
当 main.js 加载这两个模块时,它们都已经完成加载。 因此,该程序的输出会是:

$ node main.js
 main 开始
 a 开始
 b 开始
 在 b 中,a.done = false
 b 结束
 在 a 中,b.done = true
 a 结束
 在 main 中,a.done=true,b.done=true

__dirname

说明:

当前模块的文件夹名称。等同于 __filename 的 path.dirname() 的值。

demo:

console.log(__dirname);
// /Users/xiaoqiang/Documents/work/demo/NodeApi

__filename

说明:

当前模块的文件名称—解析后的绝对路径。
在主程序中这不一定要跟命令行中使用的名称一致。

demo:

console.log(__filename);
// /Users/xiaoqiang/Documents/work/demo/NodeApi/app.js

module.exports

说明:

module.exports 对象是由模块系统创建的。
有时这是难以接受的;许多人希望他们的模块成为某个类的实例。
为了实现这个,需要将期望导出的对象赋值给 module.exports。
注意,将期望的对象赋值给 exports 会简单地重新绑定本地 exports 变量,这可能不是期望的。
注意,对 module.exports 的赋值必须立即完成。 不能在任何回调中完成。

demo:

// a.js
const EventEmitter = require('events');
module.exports = new EventEmitter();
// app.js
const a = require('./a');
a.on('ready', () => {
  console.log('模块 a 已准备好');
});

exports

说明:

这是一个对于 module.exports 的更简短的引用形式。
exports 变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予 module.exports 的值。
它有一个快捷方式,以便 module.exports.f = …
可以被更简洁地写成 exports.f = …。
注意,就像任何变量,如果一个新的值被赋值给 exports,它就不再绑定到 module.exports:

demo:

// a.js
const EventEmitter = require('events');
exports.events = new EventEmitter();
// app.js
const {events} = require('./a');
events.on('ready', () => {
  console.log('模块 a 已准备好');
});

require()

说明:

使用该方法引入模块。

demo:

const {events} = require('./a');
events.on('ready', () => {
  console.log('模块 a 已准备好');
});

require.main

说明:

当 Node.js 直接运行一个文件时,require.main 会被设为它的 module。
这意味着可以通过 require.main === module 来判断一个文件是否被直接运行:
对于 foo.js 文件,如果通过 node foo.js 运行则为 true,但如果通过 require(‘./foo') 运行则为 false。
因为 module 提供了一个 filename 属性(通常等同于 __filename),
所以可以通过检查 require.main.filename 来获取当前应用程序的入口点。

demo:

const events = require('./a');
console.log(require.main);
// Module {
//  id: '.',
//  exports: {},
//  parent: null,
//  filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js',
//  loaded: false,
//  children:
//  [ Module {
//    id: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//    exports: [Object],
//    parent: [Circular],
//    filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//    loaded: true,
//    children: [],
//    paths: [Array] } ],
//  paths:
//  [ '/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules',
//   '/Users/xiaoqiang/Documents/work/demo/node_modules',
//   '/Users/xiaoqiang/Documents/work/node_modules',
//   '/Users/xiaoqiang/Documents/node_modules',
//   '/Users/xiaoqiang/node_modules',
//   '/Users/node_modules',
//   '/node_modules' ]
// }

require.cache

说明:

被引入的模块将被缓存在这个对象中。
从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块。
注意不能删除 native addons(原生插件),因为它们的重载将会导致错误。

demo:

const events = require('./a');
console.log(require.cache);
// { '/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js':
//  Module {
//   id: '.',
//   exports: {},
//   parent: null,
//   filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js',
//   loaded: false,
//   children: [ [Module] ],
//   paths:
//    [ '/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules',
//     '/Users/xiaoqiang/Documents/work/demo/node_modules',
//     '/Users/xiaoqiang/Documents/work/node_modules',
//     '/Users/xiaoqiang/Documents/node_modules',
//     '/Users/xiaoqiang/node_modules',
//     '/Users/node_modules',
//     '/node_modules' ] },
//  '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js':
//  Module {
//   id: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//   exports: { events: [EventEmitter] },
//   parent:
//    Module {
//     id: '.',
//     exports: {},
//     parent: null,
//     filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js',
//     loaded: false,
//     children: [Array],
//     paths: [Array] },
//   filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//   loaded: true,
//   children: [],
//   paths:
//    [ '/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules',
//     '/Users/xiaoqiang/Documents/work/demo/node_modules',
//     '/Users/xiaoqiang/Documents/work/node_modules',
//     '/Users/xiaoqiang/Documents/node_modules',
//     '/Users/xiaoqiang/node_modules',
//     '/Users/node_modules',
//     '/node_modules' ]
//   }
// }

require.extensions (已废弃)

说明:

指示 require 怎样处理特定的文件扩展名。
以前这被用来将非 JavaScript 模块按需编译后加载到 Node.js 中。
然而,在实践中,有更多更好的解决方案,比如用其它 Node.js 程序加载模块, 或者提前将它们编译为 JavaScript 模块。
由于模块系统已锁定,这个特性可能永远不会消失,但是鉴于其复杂性和可能导致的小问题, 最好不要碰它。
例如:把 .sjs 文件当做 .js 文件处理:

demo:

require.extensions['.sjs'] = require.extensions['.js'];

require.resolve(request[, options])

说明:

使用内部的 require() 机制查询模块的位置, 此操作只返回解析后的文件名,不会加载该模块。
request:需要解析的模块路径。
options.paths:解析模块的起点路径数组。此参数存在时,将使用这些路径而非默认解析路径。

demo:

const events = require('./a');
console.log( require.resolve('./a') );
// /Users/xiaoqiang/Documents/work/demo/NodeApi/a.js

require.resolve.paths(request)

说明:

返回一个数组,其中包含解析 request 过程中被查询的路径。
request:被查询解析路径的模块的路径。

demo:

const events = require('./a');
console.log( require.resolve.paths('./a') );
// [ '/Users/xiaoqiang/Documents/work/demo/NodeApi' ]

module

说明:

返回对当前模块的引用,是一个module对象。

demo:

const events = require('./a');
console.log( module );
// Module {
//  id: '.',
//  exports: {},
//  parent: null,
//  filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js',
//  loaded: false,
//  children:
//  [ Module {
//    id: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//    exports: [Object],
//    parent: [Circular],
//    filename: '/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js',
//    loaded: true,
//    children: [],
//    paths: [Array] } ],
//  paths:
//  [ '/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules',
//   '/Users/xiaoqiang/Documents/work/demo/node_modules',
//   '/Users/xiaoqiang/Documents/work/node_modules',
//   '/Users/xiaoqiang/Documents/node_modules',
//   '/Users/xiaoqiang/node_modules',
//   '/Users/node_modules',
//   '/node_modules' ]
// }

module 对象

说明:

在每个模块中,module 的自由变量是一个指向表示当前模块的对象的引用。
为了方便,module.exports 也可以通过全局模块的 exports 对象访问。
module 实际上不是全局的,而是每个模块本地的。

module.id

说明:

返回模块的标识符。 通常是完全解析后的文件名。

demo:

const events = require('events');
console.log( module.id );
// .

module.parent

说明:

最先引用该模块的模块。

demo:

const events= require('events');
console.log( module.parent );
// null

module.children

说明:

返回被该模块引用的模块对象。

demo:

const events = require('events');
console.log( module.children );
// []

module.filename

说明:

返回模块的完全解析后的文件名。

demo:

const events = require('events');
console.log( module.filename );
// /Users/xiaoqiang/Documents/work/demo/NodeApi/app.js

module.loaded

说明:

返回模块是否已经加载完成,或正在加载中。

demo:

const events = require('events');
console.log( module.loaded );
// false

module.paths

说明:

返回模块的搜索路径。

demo:

const events = require('events');
console.log( module.paths );
// [ '/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules',
//  '/Users/xiaoqiang/Documents/work/demo/node_modules',
//  '/Users/xiaoqiang/Documents/work/node_modules',
//  '/Users/xiaoqiang/Documents/node_modules',
//  '/Users/xiaoqiang/node_modules',
//  '/Users/node_modules',
//  '/node_modules' ]

希望本文所述对大家node.js程序设计有所帮助。

(0)

相关推荐

  • Node.js API详解之 vm模块用法实例分析

    本文实例讲述了Node.js API详解之 vm模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 vm vm 模块提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码. JavaScript 代码可以被编译并立即运行,或编译.保存然后再运行. 常见的用法是在沙盒中运行代码.沙盒代码使用不同的V8上下文. const vm = require('vm'); const x = 1; const sandbox = { x: 2 }; vm.createContex

  • Node.js API详解之 timer模块用法实例分析

    本文实例讲述了Node.js API详解之 timer模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 timer timer 模块暴露了一个全局的 API,用于在某个未来时间段调用调度函数. 因为定时器函数是全局的,所以使用该 API 无需调用 require('timers'). Node.js 中的计时器函数实现了与 Web 浏览器提供的定时器类似的 API, 它使用了一个不同的内部实现,它是基于 Node.js 事件循环构建的. setImmediate(call

  • Node.js API详解之 Error模块用法实例分析

    本文实例讲述了Node.js API详解之 Error模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 Error Node.js 中运行的应用程序一般会遇到以下四类错误: 1.标准的 JavaScript 错误: EvalError : 当调用 eval() 失败时抛出. SyntaxError : 当 JavaScript 语法错误时抛出. RangeError : 当值不在预期范围内时抛出. ReferenceError : 当使用未定义的变量时抛出. TypeEr

  • Node.js API详解之 assert模块用法实例分析

    本文实例讲述了Node.js API详解之 assert模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 assert assert 模块提供了断言测试的函数,用于测试不变式. 断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真, 可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言. 同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言. 使用断言可以创建更稳定.品质更好且不易于出错的代码. 当需要在一个值为

  • Node.js API详解之 querystring用法实例分析

    本文实例讲述了Node.js API详解之 querystring用法.分享给大家供大家参考,具体如下: Node.js API详解之 querystring querystring模块提供了一些实用函数,用于解析与格式换URL查询字符串 通过 const querystring = require('querystring'); 的方式引用querystrings模块 目录: querystring.escape(str) querystring.unescape(str) querystri

  • Node.js API详解之 string_decoder用法实例分析

    本文实例讲述了Node.js API详解之 string_decoder用法.分享给大家供大家参考,具体如下: string_decoder 模块提供了一个 API,用于把 Buffer 对象解码成字符串. 对于参数末尾不完整的多字节字符,string_decoder会将其保存在内部的buffer中,当再次解码时,补充到参数开头. 通过 const { StringDecoder } = require('string_decoder'); 的方式引用string_decoder模块. 目录:

  • Node.js API详解之 os模块用法实例分析

    本文实例讲述了Node.js API详解之 os模块用法.分享给大家供大家参考,具体如下: os是Node的一个基础模块,提供了一些操作系统相关的属性接口. 通过 const os = require('os'); 的方式引用os模块,我们接着看下os都有哪些功能 说明: 一个字符串常量,定义操作系统相关的行末标志:POSIX 系统上值为\n,Windows下的值为\r\n demo: console.log('abc'+ os.EOL +'123'); //abc //123 os.arch(

  • Node.js API详解之 zlib模块用法分析

    本文实例讲述了Node.js API详解之 zlib模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 zlib zlib模块提供通过 Gzip 和 Deflate/Inflate 实现的压缩功能,可以通过这样使用它: const zlib = require('zlib'); 压缩或者解压数据流(例如一个文件)通过zlib流将源数据流传输到目标流中来完成: const gzip = zlib.createGzip(); const fs = require('fs');

  • Node.js API详解之 util模块用法实例分析

    本文实例讲述了Node.js API详解之 util模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 util util 模块主要用于支持 Node.js 内部 API 的需求.提供了大部分实用工具. 通过 const util = require('util'); 的方式引用util模块 util.callbackify(original) 说明: original:传递一个 async 函数,或者是一个返回Promise的异步函数. callbackify会返回一个方

  • Node.js API详解之 tty功能与用法实例分析

    本文实例讲述了Node.js API详解之 tty功能与用法.分享给大家供大家参考,具体如下: tty 可以理解为终端的意思.tty 模块提供终端相关的接口,用来获取终端的行数列数等. 通过 const tty = require('tty'); 的方式引用 tty 模块 process.stdout.isTTY 说明: 此属性位于 process 模块,用来判断 Node.js 是否运行在一个 TTY 环境中 demo: console.log(process.stdout.isTTY) //

  • Node.js API详解之 V8模块用法实例分析

    本文实例讲述了Node.js API详解之 V8模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 V8 v8 模块暴露了特定于V8版本内置到 Node.js 二进制文件中的API. 通过以下方式使用: const v8 = require('v8'); 注意: APIs 和实现可能在任何时间变动. v8.cachedDataVersionTag() 说明: 返回一个表示从V8版本,命令行标志和已检测到的CPU功能派生的"version tag"的整数. 这对于判

  • Node.js API详解之 readline模块用法详解

    本文实例讲述了Node.js API详解之 readline模块用法.分享给大家供大家参考,具体如下: Node.js API详解之 readline readline 模块提供了一个接口,用于从可读流(如 process.stdin)读取数据,每次读取一行. 它可以通过以下方式使用: const readline = require('readline'); readline 模块的基本用法: const readline = require('readline'); const rl = r

随机推荐