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自适应IFrame高度(支持嵌套 兼容IE,ff,safafi,chrome)
Mar 28 Javascript
Eclipse去除js(JavaScript)验证错误
Feb 11 Javascript
使用typeof判断function是否存在于上下文
Aug 14 Javascript
Javascript设计模式理论与编程实战之简单工厂模式
Nov 03 Javascript
浅谈js中对象的使用
Aug 11 Javascript
JavaScript 中对象的深拷贝
Dec 04 Javascript
jQuery Collapse1.1.0折叠插件简单使用
Aug 28 jQuery
react native与webview通信的示例代码
Sep 25 Javascript
微信小程序基于slider组件动态修改标签透明度的方法示例
Dec 04 Javascript
jQuery实现获取动态添加的标签对象示例
Jun 28 jQuery
vue+element UI实现树形表格带复选框的示例代码
Apr 16 Javascript
vue draggable resizable 实现可拖拽缩放的组件功能
Jul 15 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
如何将一个表单同时提交到两个地方处理
2006/10/09 PHP
PHP 字符串编码截取函数(兼容utf-8和gb2312)
2009/05/02 PHP
PHP学习 变量使用总结
2011/03/24 PHP
实例简介PHP的一些高级面向对象编程的特性
2015/11/27 PHP
Jquery 插件开发笔记整理
2011/01/17 Javascript
javascript实现点击单选按钮链接转向对应网址的方法
2015/08/12 Javascript
深入理解js promise chain
2016/05/05 Javascript
关于动态执行代码(js的Eval)实例详解
2016/08/15 Javascript
canvas实现探照灯效果
2017/02/07 Javascript
Vue中的v-cloak使用解读
2017/03/27 Javascript
浅谈JavaScript中的属性:如何遍历属性
2017/09/14 Javascript
IntelliJ IDEA 安装vue开发插件的方法
2017/11/21 Javascript
JavaScript调用模式与this关键字绑定的关系
2018/04/21 Javascript
详解在不使用ssr的情况下解决Vue单页面SEO问题
2018/11/08 Javascript
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
关于JS模块化的知识点分享
2019/10/16 Javascript
vue实现选中效果
2020/10/07 Javascript
[03:09]2014DOTA2国际邀请赛 Mushi前队友送上祝福
2014/07/12 DOTA
浅谈Python 中整型对象的存储问题
2016/05/16 Python
老生常谈python的私有公有属性(必看篇)
2017/06/09 Python
对numpy中数组元素的统一赋值实例
2018/04/04 Python
初次部署django+gunicorn+nginx的方法步骤
2019/09/11 Python
Python使用matplotlib 画矩形的三种方式分析
2019/10/31 Python
在Django下创建项目以及设置settings.py教程
2019/12/03 Python
windows下python安装pip方法详解
2020/02/10 Python
python中setuptools的作用是什么
2020/06/19 Python
序列化Python对象的方法
2020/08/01 Python
Python urllib3软件包的使用说明
2020/11/18 Python
Python实现简单的2048小游戏
2021/03/01 Python
CSS3中媒体查询结合rem布局适配手机屏幕
2019/06/10 HTML / CSS
SQL Server数据库笔试题和答案
2016/02/04 面试题
电工工作职责范本
2014/02/22 职场文书
新春联欢会主持词
2014/03/24 职场文书
六一节目主持词
2014/04/01 职场文书
签订劳动合同通知书
2015/04/16 职场文书
2016国庆节活动宣传语
2015/11/25 职场文书