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极简入门教程(三):进程
Oct 27 NodeJs
NodeJS学习笔记之(Url,QueryString,Path)模块
Jan 13 NodeJs
浅谈Nodejs观察者模式
Oct 13 NodeJs
使用NodeJs 开发微信公众号(三)微信事件交互实例
Mar 02 NodeJs
在windows上用nodejs搭建静态文件服务器的简单方法
Aug 11 NodeJs
NodeJS学习笔记之Module的简介
Mar 24 NodeJs
NodeJs使用Mysql模块实现事务处理实例
May 31 NodeJs
nodejs创建简易web服务器与文件读写的实例
Sep 07 NodeJs
nodejs实现截取上传视频中一帧作为预览图片
Dec 10 NodeJs
详解NODEJS的http实现
Jan 04 NodeJs
nodejs使用async模块同步执行的方法
Mar 02 NodeJs
Nodejs监控事件循环异常示例详解
Sep 22 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
全国FM电台频率大全 - 27 陕西省
2020/03/11 无线电
PHP实现分页的一个示例
2006/10/09 PHP
基于php常用正则表达式的整理汇总
2013/06/08 PHP
PHP中VC6、VC9、TS、NTS版本的区别与用法详解
2013/10/26 PHP
mac下Apache + MySql + PHP搭建网站开发环境
2014/06/02 PHP
php获取本周开始日期和结束日期的方法
2015/03/09 PHP
jQuery控制图片的hover效果(smartRollover.js)
2012/03/18 Javascript
页面加载完后自动执行一个方法的js代码
2014/09/06 Javascript
jQuery构造函数init参数分析
2015/05/13 Javascript
jquery实现平滑的二级下拉菜单效果
2015/08/26 Javascript
js游戏人物上下左右跑步效果代码分享
2015/08/28 Javascript
jQuery实现带有洗牌效果的动画分页实例
2015/08/31 Javascript
jQuery解决浏览器兼容性问题案例分析
2016/04/15 Javascript
基于jQuery的ajax方法封装
2016/07/14 Javascript
jQuery中Datatables增加跳转到指定页功能
2017/02/08 Javascript
扩展bootstrap的modal模态框-动态添加modal框-弹出多个modal框
2017/02/21 Javascript
angular使用bootstrap方法手动启动的实例代码
2017/07/18 Javascript
jquery实现下拉菜单的手风琴效果
2017/07/23 jQuery
vue-cli2.9.3 详细教程
2018/04/23 Javascript
JS访问对象两种方式区别解析
2020/08/29 Javascript
对vue生命周期的深入理解
2020/12/03 Vue.js
python关闭windows进程的方法
2015/04/18 Python
在Python的Django框架中使用通用视图的方法
2015/07/21 Python
十条建议帮你提高Python编程效率
2016/02/16 Python
MySQL适配器PyMySQL详解
2017/09/20 Python
Python3+OpenCV2实现图像的几何变换(平移、镜像、缩放、旋转、仿射)
2019/05/13 Python
CSS3 background-image颜色渐变的实现代码
2018/09/13 HTML / CSS
东南亚地区最大的购物网站Lazada新加坡站点:Lazada.sg
2016/07/17 全球购物
华丽的手绘陶瓷:MacKenzie-Childs
2017/02/04 全球购物
Hanky Panky官方网站:内衣和睡衣
2019/07/25 全球购物
运动会演讲稿100字
2014/08/25 职场文书
2015年中学图书馆工作总结
2015/07/22 职场文书
网络新闻该怎么写?这些写作技巧你都知道吗?
2019/08/26 职场文书
详解Vue的options
2021/05/15 Vue.js
Smart 2 车辆代号 HC11 全新谍照曝光
2022/04/21 数码科技
PYTHON 使用 Pandas 删除某列指定值所在的行
2022/04/28 Python