NodeJS学习笔记之Module的简介


Posted in NodeJs onMarch 24, 2017

Node.js模块系统

Node.js有一个简单的模块加载系统。 在Node.js中,文件和模块是一一对应的(每个文件被视为单独的模块)。

例如,考虑下面这个名为 foo.js 的文件:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

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

circle.js 的内容如下:

const PI = Math.PI;

exports.area = (r) => PI * r * r;

exports.circumference = (r) => 2* PI * r;

模块 circle.js 导出了函数 area() circumference() 。 要将函数和对象添加到模块的根目录,可以将它们赋值到特殊 exports 对象上。

模块内部的变量一定是私有的,因为模块被Node.js包裹在一个函数中(参见下面的模块包装器)。 在这个例子中,变量 PI 对于 circle.js 来说是私有变量。

如果你希望模块导出的是一个函数(如构造函数),或者是要导出完整的对象,而不是一次创建一个属性,则需要将其分配给 module.exports 而不是 exports 。

在下面的 bar.js 中,使用了 square 模块,它导出一个构造函数:

const square = require('./square.js');
var mySquare = square(2);
console.log(`The area of my square is ${mySquare.area()}`);

在 square.js 模块中定义一个 square 方法:

module.exports = (width) => {
  return {
    area: () => width * width;
  };
}

此外,模块系统在 require(“module”) 模块中实现。

『main』模块

当某个 module 直接从Node.js运行时,它会将 require.main 设置该 module 。 你可以通过这个来测试这个 module 是被直接运行的还是被 require 的。

require.main === module

就拿文件 foo.js 来说,如果运行 node foo.js 这个属性就是 true 。运行 require('./foo.js') 就是 false 。

因为 module 提供了一个 filename (通常相当于 __filename ),因此可以通过检查 require.main.filename 来获取当前应用程序的入口点。

包管理器的一些提示

Node.js的 require() 函数支持一些合理的目录结构。它让软件包管理器程序(如 dpkg , rpm 和 npm )可以从Node.js模块中直接去构建本地的包而不需要修改。

下面我们给出一个可以正常工作的建议目录结构:

假设我们希望在 /usr/lib/node/<some-package>/<some-version> 中的文件夹来指定版本的包。

此外,包还可以相互依赖。 比如你想安装 foo 包,而这个包有可能需要安装指定版本的 bar 包。而 bar 包也很有可能依赖其他的包,并且在某些特殊情况下,这些依赖包甚至可能会产生循环依赖。

由于Node.js会查找加载的所有模块的 realpath (即解析软链),然后再去node_modules文件夹中查找依赖的包,因此使用以下方案可以非常简单地解决此问题:

/usr/lib/node/foo/1.2.3/ - 包含 foo 包,版本是 1.2.3

/usr/lib/node/bar/4.3.2/ - 包含 foo 所依赖的 bar 包

/usr/lib/node/foo/1.2.3/node_modules/bar - 软链到 /usr/lib/node/bar/4.3.2/

/usr/lib/node/bar/4.3.2/node_modules/* - 软链到 bar 的依赖

因此,即使遇到循环依赖,或者是依赖冲突,每个模块都能加载到并使用自己所依赖指定版本的包。

当 foo 包中 require('bar') 时,它就可以软链到指定版本的 /usr/lib/node/foo/1.2.3/node_modules/bar 。然后,当 bar 包中的代码调用 require('quux') 时,它同样也可以软链到指定版本的 /usr/lib/node/bar/4.3.2/node_modules/quux 。

模块加载的全过程(重点,下面写的伪代码流程一定要记住)

要获取在调用 require() 将被加载的确切文件名,请使用 require.resolve() 函数。

以下是模块加载的全过程以及 require.resolve 的解析过程:

// 加载X模块
require(X) from module at path Y
1. If X is a core module.
  a. return the core module
  b. STOP
2. If X begins with './' or '/' or '../'
  a. LOAD_AS_FILE(Y + X)
  b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"

// 加载X文件
// 加载过程:X -> X.js -> X.json -> X.node
LOAD_AS_FILE(X)
1. If [X] is a file, load [X] as JavaScript text. STOP
2. If [X.js] is a file, load [X.js] as JavaScript text. STOP
3. If [X.json] is a file, load [X.json] as JavaScript text. STOP
4. If [X.node] is a file, load [X.node] as JavaScript text. STOP

// 加载入口文件
// 加载过程:X -> X/index.js -> X/index.json -> X/index.node
LOAD_INDEX(X)
1. If [X/index.js] is a file, load [X/index.js] as JavaScript text. STOP
2. If [X/index.json] is a file, load [X/index.json] as JavaScript text. STOP
3. If [X/index.node] if a file, load [X/index.node] as JavaScript text. STOP

// 加载文件夹
LOAD_AS_DIRECTORY(X)
1. If [X/package.json] is a file.
  a. Parse [X/package.json], and look for "main" field
  b. let M = X + (json main field)
  c. LOAD_AS_FILE(M)
  d. LOAD_INDEX(M)
2. LOAD_INDEX(X)
 
// 加载node模块
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS;
  a. LOAD_AS_FILE(DIR/X)
  b. LOAD_AS_DIRECTORY(DIR/X)

// 列出所有可能的node_modules路径
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 DIRS

模块缓存

所有的模块都会在第一次加载之后被缓存起来。 这意味着你每次调用 require('foo') 将得到完全相同的对象。

对 require('foo') 的多次调用可能并不会多次执行该模块的代码。 这是一个重要的功能。 使用它,可以返回“partially done”对象,从而允许根据依赖关系一层一层地加载模块,即使这样做可能会导致循环依赖。

如果要让某个模块在每次被加载时都去执行代码,则需要 exports 一个函数,并调用该函数即可。

模块缓存注意事项

模块是基于其解析出来的文件名进行缓存。根据调用模块的路径,被调用的模块可能会解析出不同的文件名(从node_modules文件夹加载)。如果解析出来的是不同的文件,它不保证每次 require('foo') 总是返回相同的对象。

另外,在不区分大小写的文件系统或操作系统上,不同的解析文件名可以指向相同的文件,但缓存仍将它们视为不同的模块,并将重新加载该文件多次。 例如, require('./ foo') 和 require('./ FOO') 返回两个不同的对象,而不管 ./foo 和 ./FOO 是否是同一个文件。

核心模块

Node.js有些模块被编译成二进制文件。 本文档中的其他部分将对这些模块进行更详细的描述。

核心模块在Node.js的源码 lib/ 文件夹中。

如果核心模块的模块标识传递给 require() ,则它们总是优先加载。 例如,即使有一个自定义模块叫 http ,我们去执行 require('http') 也将始终返回内置的 HTTP 模块,

循环引用

当循环引用 require() 时,返回模块可能并没有执行完成。

考虑这种情况:

a.js :

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js :

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

app.js :

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);

当 app.js 加载 a.js 时, a.js 依次加载 b.js . 此时, b.js 尝试加载 a.js . 为了防止无限循环,将 a.js 导出对象的未完成副本返回到 b.js 模块。 b.js 然后完成加载,并将其导出对象提供给 a.js 模块。

当 app.js 加载了这两个模块时,它们都已经完成。 因此,该程序的输出将是:

$ node app.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
in main, a.done =true, b.done = true

模块包装器

在执行模块的代码之前,Node.js将使用一个函数包装器来将模块内容包裹起来,如下所示:

(function (exports, require, module, __filename, __dirname) {
  // 你的模块代码
});

通过这样做,Node.js实现了以下几点:

它将模块内部的顶级变量(定义为 var , const 或 let )的作用域范围限定为模块内部而不是全局。

它有助于给模块内部提供一些实际上只属于该模块的全局变量,例如:

module 和 exports 对象用来帮助从模块内部导出一些值

变量 __filename 和 __dirname 是当前模块最终解析出来的文件名和文件夹路径

module 对象签名

Object module {
  id: String, // 模块标识,为该模块文件在系统中的绝对路径
  exports: Object, // 该模块的导出对象
  parent: Object | undefined, // 引用该模块的父模块
  filename: String | null, // 最终解析的文件名称, 与__filename相同。
  loaded: Boolean, // 该模块是否已经加载
  children: Array, // 改模块的引用列表
  paths: Array // 模块加载路径
}

require 函数签名

Function require {
  [Function], // 函数体
  resolve: Function, // 根据模块标识解析模块,返回绝对路径
  main: undefined | Object, // 应用的主(main)模块
  extensions: {'.js':Function, '.json':Function, '.node':Function},
  cache: Object // 模块缓存,以模块的绝对路径为key
}
NodeJs 相关文章推荐
nodejs npm install全局安装和本地安装的区别
Jun 05 NodeJs
我的NodeJs学习小结(一)
Jul 06 NodeJs
nodejs实现HTTPS发起POST请求
Apr 23 NodeJs
使用DNode实现php和nodejs之间通信的简单实例
Jul 06 NodeJs
NodeJS与HTML5相结合实现拖拽多个文件上传到服务器的实现方法
Jul 26 NodeJs
NodeJS测试框架mocha入门教程
Mar 28 NodeJs
NodeJS基础API搭建服务器详细过程记录
Apr 01 NodeJs
详解nodeJS之二进制buffer对象
Jun 03 NodeJs
Express+Nodejs 下的登录拦截实现代码
Jul 01 NodeJs
nodejs结合socket.io实现websocket通信功能的方法
Jan 12 NodeJs
nodejs 最新版安装npm 的使用详解
Jan 18 NodeJs
nodejs实现的简单web服务器功能示例
Mar 15 NodeJs
详解nodejs中的process进程
Mar 19 #NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 #NodeJs
nodejs中向HTTP响应传送进程的输出
Mar 19 #NodeJs
实例分析nodejs模块xml2js解析xml过程中遇到的坑
Mar 18 #NodeJs
nodejs中模块定义实例详解
Mar 18 #NodeJs
Nodejs基于LRU算法实现的缓存处理操作示例
Mar 17 #NodeJs
用nodeJS搭建本地文件服务器的几种方法小结
Mar 16 #NodeJs
You might like
基于OpenCV的PHP图像人脸识别技术
2009/10/11 PHP
PHP数组的交集array_intersect(),array_intersect_assoc(),array_inter_key()函数的小问题
2011/05/29 PHP
PHP中file_get_contents高?用法实例
2014/09/24 PHP
php采集自中央气象台范围覆盖全国的天气预报代码实例
2015/01/04 PHP
fckeditor上传文件按日期存放及重命名方法
2015/05/22 PHP
js新闻滚动 js如何实现新闻滚动效果
2013/01/07 Javascript
Js-$.extend扩展方法使方法参数更灵活
2013/01/15 Javascript
JQuery for与each性能比较分析
2013/05/14 Javascript
jquery批量控制form禁用的代码
2013/08/06 Javascript
JS使用replace()方法和正则表达式进行字符串的搜索与替换实例
2014/04/10 Javascript
三种取消选中单选框radio的方法
2014/09/09 Javascript
JavaScript实现将文本框的值插入指定位置的方法
2015/08/13 Javascript
JS简单实现表格排序功能示例
2016/12/20 Javascript
Vue.js系列之vue-router(上)(3)
2017/01/03 Javascript
canvas绘制环形进度条
2017/02/23 Javascript
使用JS编写的随机抽取号码的小程序
2017/08/11 Javascript
小程序ios音频播放没声音问题的解决
2018/07/11 Javascript
JS使用Date对象实时显示当前系统时间简单示例
2018/08/23 Javascript
Vue + Node.js + MongoDB图片上传组件实现图片预览和删除功能详解
2020/04/29 Javascript
Python实现将照片变成卡通图片的方法【基于opencv】
2018/01/17 Python
Python使用min、max函数查找二维数据矩阵中最小、最大值的方法
2018/05/15 Python
解决Django加载静态资源失败的问题
2019/07/28 Python
ansible动态Inventory主机清单配置遇到的坑
2020/01/19 Python
python实现图像拼接功能
2020/03/23 Python
JupyterNotebook 输出窗口的显示效果调整实现
2020/09/22 Python
matplotlib bar()实现多组数据并列柱状图通用简便创建方法
2021/02/24 Python
VIVOBAREFOOT赤脚鞋:让您的脚做自然的事情
2017/06/01 全球购物
Hotels.com泰国:酒店预订网站
2019/11/20 全球购物
代理商会议邀请函
2014/01/27 职场文书
高考备战决心书
2014/03/11 职场文书
贷款担保申请书
2014/05/20 职场文书
医生爱岗敬业演讲稿
2014/08/26 职场文书
2015暑假社会调查报告
2015/07/13 职场文书
2015年音乐教研组工作总结
2015/07/22 职场文书
2016优秀班主任个人先进事迹材料
2016/02/26 职场文书
一文搞懂redux在react中的初步用法
2021/06/09 Javascript