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


Posted in Javascript onMay 13, 2020

本文实例讲述了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程序设计有所帮助。

Javascript 相关文章推荐
jquery关于图形报表的运用实现代码
Jan 06 Javascript
jquery怎样实现ajax联动框(二)
Mar 08 Javascript
js创建表单元素并使用submit进行提交
Aug 14 Javascript
Javascript进制转换实例分析
May 14 Javascript
js实现简单锁屏功能实例
May 27 Javascript
WebSocket+node.js创建即时通信的Web聊天服务器
Aug 08 Javascript
使用JS实现图片展示瀑布流效果(简单实例)
Sep 06 Javascript
JS中Safari浏览器中的Date
Jul 17 Javascript
webpack本地开发环境无法用IP访问的解决方法
Mar 20 Javascript
vue删除html内容的标签样式实例
Sep 13 Javascript
如何在vue项目中嵌入jsp页面的方法(2种)
Feb 06 Javascript
vue动态加载SVG文件并修改节点数据的操作代码
Aug 17 Javascript
JS如何实现手机端输入验证码效果
May 13 #Javascript
vue-socket.io接收不到数据问题的解决方法
May 13 #Javascript
Vue关于组件化开发知识点详解
May 13 #Javascript
Vue 中获取当前时间并实时刷新的实现代码
May 12 #Javascript
JavaScript 函数用法详解【函数定义、参数、绑定、作用域、闭包等】
May 12 #Javascript
JavaScript 面向对象程序设计详解【类的创建、实例对象、构造函数、原型等】
May 12 #Javascript
JavaScript 接口原理与用法实例详解
May 12 #Javascript
You might like
PhpMyAdmin出现export.php Missing parameter: what /export_type错误解决方法
2012/08/09 PHP
探讨多键值cookie(php中cookie存取数组)的详解
2013/06/06 PHP
解析PHP中ob_start()函数的用法
2013/06/24 PHP
php中smarty变量修饰用法实例分析
2015/06/11 PHP
详解WordPress中调用评论模板和循环输出评论的PHP函数
2016/01/05 PHP
PHP文件及文件夹操作之创建、删除、移动、复制
2016/07/13 PHP
使用JavaScript库还是自己写代码?
2010/01/28 Javascript
对象无length属性时IE6/IE7中无法将其转换成伪数组(ArrayLike)
2011/07/31 Javascript
Js中setTimeout()和setInterval() 何时被调用执行的用法
2013/04/12 Javascript
60行js代码实现俄罗斯方块
2015/03/31 Javascript
JS实现无限级网页折叠菜单(类似树形菜单)效果代码
2015/09/17 Javascript
如何解决ligerUI布局时Center中的Tab高度大小
2015/11/24 Javascript
一个简单的JavaScript Map实例(分享)
2016/08/03 Javascript
js判断文件格式及大小的简单实例(必看)
2016/10/11 Javascript
js数字舍入误差以及解决方法(必看篇)
2017/02/28 Javascript
jquery ui sortable拖拽后保存位置
2017/04/27 jQuery
详解Angular路由 ng-route和ui-router的区别
2017/05/22 Javascript
AngularJS表单验证功能
2017/10/19 Javascript
浅谈VUE监听窗口变化事件的问题
2018/02/24 Javascript
vue组件name的作用小结
2018/05/23 Javascript
vue 解决data中定义图片相对路径页面不显示的问题
2020/08/13 Javascript
[02:03]DOTA2亚洲邀请赛 HGT战队出场宣传片
2015/02/07 DOTA
对python3新增的byte类型详解
2018/12/04 Python
python对于requests的封装方法详解
2019/01/03 Python
GitHub 热门:Python 算法大全,Star 超过 2 万
2019/04/29 Python
python实现将文件夹内的每张图片批量分割成多张
2019/07/22 Python
Python爬虫使用浏览器cookies:browsercookie过程解析
2019/10/22 Python
Python通过正则库爬取淘宝商品信息代码实例
2020/03/02 Python
Python如何把Spark数据写入ElasticSearch
2020/04/18 Python
python语言的优势是什么
2020/06/17 Python
澳大利亚家庭花园和DIY工具网店:VidaXL
2019/05/03 全球购物
机关党员2014全国两会学习心得体会
2014/03/10 职场文书
五四青年节的活动方案
2014/08/20 职场文书
励志演讲稿300字
2014/08/21 职场文书
餐饮服务员岗位职责
2015/02/09 职场文书
如何用 Python 子进程关闭 Excel 自动化中的弹窗
2021/05/07 Python