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 相关文章推荐
点图片上一页下一页翻页效果
Jul 09 Javascript
深入解析contentWindow, contentDocument
Jul 04 Javascript
JS实现点击颜色块切换指定区域背景颜色的方法
Feb 25 Javascript
angularjs学习笔记之简单介绍
Sep 26 Javascript
js老生常谈之this,constructor ,prototype全面解析
Apr 05 Javascript
鼠标经过出现气泡框的简单实例
Mar 17 Javascript
vue货币过滤器的实现方法
Apr 01 Javascript
jQuery获取复选框选中的当前行的某个字段的值
Sep 15 jQuery
对vue事件的延迟执行实例讲解
Aug 28 Javascript
js实现简单页面全屏
Sep 17 Javascript
浅析vue中的provide / inject 有什么用处
Nov 10 Javascript
详解uniapp的全局变量实现方式
Jan 11 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
php数组函数序列之asort() - 对数组的元素值进行升序排序,保持索引关系
2011/11/02 PHP
2个自定义的PHP in_array 函数,解决大量数据判断in_array的效率问题
2014/04/08 PHP
PHP实现克鲁斯卡尔算法实例解析
2014/08/22 PHP
详解PHP的Yii框架中组件行为的属性注入和方法注入
2016/03/18 PHP
PHP用continue跳过本次循环中剩余代码的注意点
2017/06/27 PHP
PHP实现的pdo连接数据库并插入数据功能简单示例
2019/03/30 PHP
用jquery实现学校的校历(asp.net+jquery ui 1.72)
2010/01/01 Javascript
JavaScript取得鼠标绝对位置程序代码介绍
2012/09/16 Javascript
Javascript 检测键盘按键信息及键码值对应介绍
2013/01/03 Javascript
javascript自启动函数的问题探讨
2013/10/05 Javascript
jquery简单实现鼠标经过导航条改变背景图
2013/12/17 Javascript
JS日期对象简单操作(获取当前年份、星期、时间)
2016/10/26 Javascript
Bootstrap导航条的使用和理解3
2016/12/14 Javascript
详解AngularJs ui-router 路由的简单介绍
2017/04/26 Javascript
利用three.js画一个3D立体的正方体示例代码
2017/11/19 Javascript
Node.js的Koa实现JWT用户认证方法
2018/05/05 Javascript
vue中英文切换实例代码
2020/01/21 Javascript
uni-app微信小程序登录授权的实现
2020/05/22 Javascript
[49:29]LGD vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
PyQt5 QTableView设置某一列不可编辑的方法
2019/06/25 Python
sklearn线性逻辑回归和非线性逻辑回归的实现
2020/06/09 Python
Python压缩模块zipfile实现原理及用法解析
2020/08/14 Python
使用css创建三角形 使用CSS3创建3d四面体原理及代码(html5实践)
2013/01/06 HTML / CSS
加拿大购物频道:The Shopping Channel
2016/07/21 全球购物
英国购买威士忌网站:Master of Malt
2019/09/26 全球购物
Servlet如何得到服务器的信息
2015/12/22 面试题
采购部部门职责
2013/12/15 职场文书
工程专业毕业生自荐信范文
2013/12/25 职场文书
商务经理岗位职责
2014/08/03 职场文书
四风问题对照检查材料思想汇报
2014/10/07 职场文书
政风行风整改方案
2014/10/25 职场文书
2014年药剂科工作总结
2014/11/26 职场文书
社区六一儿童节活动总结
2015/02/11 职场文书
幼儿园教师师德承诺书
2015/04/28 职场文书
革命电影观后感
2015/06/18 职场文书
pygame面向对象的飞行小鸟实现(Flappy bird)
2021/04/01 Python