详细分析Node.js 模块系统


Posted in Javascript onJune 28, 2020

为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

创建模块

在 Node.js 中,创建一个模块非常简单,如下我们创建一个 main.js 文件,代码如下:

var hello = require('./hello');
hello.world();

以上实例中,代码 require('./hello') 引入了当前目录下的 hello.js 文件(./ 为当前目录,node.js 默认后缀为 js)。

Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

接下来我们就来创建 hello.js 文件,代码如下:

exports.world = function() {
 console.log('Hello World');
}

在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访问接口,在 main.js 中通过 require('./hello') 加载这个模块,然后就可以直接访 问 hello.js 中 exports 对象的成员函数了。

有时候我们只是想把一个对象封装到模块中,格式如下:

module.exports = function() {
 // ...
}

例如:

//hello.js 
function Hello() { 
  var name; 
  this.setName = function(thyName) { 
    name = thyName; 
  }; 
  this.sayHello = function() { 
    console.log('Hello ' + name); 
  }; 
}; 
module.exports = Hello;

这样就可以直接获得这个对象了:

//main.js 
var Hello = require('./hello'); 
hello = new Hello(); 
hello.setName('BYVoid'); 
hello.sayHello();

模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。

服务端的模块放在哪里

也许你已经注意到,我们已经在代码中使用了模块了。像这样:

var http = require("http");

...

http.createServer(...);

Node.js 中自带了一个叫做 http 的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

Node.js 的 require 方法中的文件查找策略如下:

由于 Node.js 中存在 4 类模块(原生模块和3种文件模块),尽管 require 方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:

详细分析Node.js 模块系统

从文件模块缓存中加载

尽管原生模块与文件模块的优先级不同,但是都会优先从文件模块的缓存中加载已经存在的模块。

从原生模块加载

原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个 http/http.js/http.node/http.json 文件,require("http") 都不会从这些文件中加载,而是从原生模块中加载。

原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。

从文件加载

当文件模块缓存中不存在,而且不是原生模块的时候,Node.js 会解析 require 方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

在路径 Y 下执行 require(X) 语句执行顺序:

1. 如果 X 是内置模块
   a. 返回内置模块
   b. 停止执行
2. 如果 X 以 '/' 开头
   a. 设置 Y 为文件根路径
3. 如果 X 以 './' 或 '/' or '../' 开头
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. 抛出异常 "not found"

LOAD_AS_FILE(X)
1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。
2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。
3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。
4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。

LOAD_INDEX(X)
1. 如果 X/index.js 是一个文件,  将 X/index.js 作为 JavaScript 文本载入并停止执行。
2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。
3. 如果 X/index.node 是一个文件,  将 X/index.node 作为二进制插件载入并停止执行。

LOAD_AS_DIRECTORY(X)
1. 如果 X/package.json 是一个文件,
   a. 解析 X/package.json, 并查找 "main" 字段。
   b. let M = X + (json main 字段)
   c. LOAD_AS_FILE(M)
   d. LOAD_INDEX(M)
2. LOAD_INDEX(X)

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_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

以上就是详细分析Node.js 模块系统的详细内容,更多关于Node.js模块系统的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
爱恋千雪-US-AscII加密解密工具(网页加密)下载
Jun 06 Javascript
js控制框架刷新
Aug 01 Javascript
用js来解决ajax读取页面乱码
Nov 28 Javascript
js之onload事件的一点使用心得
Aug 14 Javascript
JS JSON对象转为字符串的简单实现方法
Nov 18 Javascript
JavaScript中常见的字符串操作函数及用法汇总
May 04 Javascript
avalonjs制作响应式瀑布流特效
May 06 Javascript
Google Maps基础及实例解析
Aug 06 Javascript
javascript 判断是否是微信浏览器的方法
Oct 09 Javascript
vue实现消息的无缝滚动效果的示例代码
Dec 05 Javascript
webpack中如何使用雪碧图的示例代码
Nov 11 Javascript
JS实现盒子跟着鼠标移动及键盘方向键控制盒子移动效果示例
Jan 29 Javascript
微信小程序实现拨打电话功能的示例代码
Jun 28 #Javascript
js瀑布流布局的实现
Jun 28 #Javascript
JavaScript如何使用插值实现图像渐变
Jun 28 #Javascript
vue实现登录拦截
Jun 29 #Javascript
Laravel 如何在blade文件中使用Vue组件的示例代码
Jun 28 #Javascript
JavaScript多种图形实现代码实例
Jun 28 #Javascript
从Node.js事件触发器到Vue自定义事件的深入讲解
Jun 26 #Javascript
You might like
php Undefined index的问题
2009/06/01 PHP
ThinkPHP3.1新特性之多层MVC的支持
2014/06/19 PHP
php从数据库读取数据,并以json格式返回数据的方法
2018/08/21 PHP
使用IE6看老赵的博客 jQuery初探
2010/01/17 Javascript
jquery入门—选择器实现隔行变色实例代码
2013/01/04 Javascript
js中document.write的那点事
2014/12/12 Javascript
使用jQuery Mobile框架开发移动端Web App的入门教程
2016/05/17 Javascript
AngularJS中的JSONP实例解析
2016/12/01 Javascript
React 项目迁移 Webpack Babel7的实现
2018/09/12 Javascript
详解vantUI框架在vue项目中的应用踩坑
2018/12/06 Javascript
Vue 嵌套路由使用总结(推荐)
2020/01/13 Javascript
vue项目使用高德地图的定位及关键字搜索功能的实例代码(踩坑经验)
2020/03/07 Javascript
python利用elaphe制作二维条形码实现代码
2012/05/25 Python
Python实现对字符串的加密解密方法示例
2017/04/29 Python
TensorFlow Session会话控制&Variable变量详解
2018/07/30 Python
python正则-re的用法详解
2019/07/28 Python
python Elasticsearch索引建立和数据的上传详解
2019/08/04 Python
python异常处理和日志处理方式
2019/12/24 Python
python读取与处理netcdf数据方式
2020/02/14 Python
Python气泡提示与标签的实现
2020/04/01 Python
Keras实现将两个模型连接到一起
2020/05/23 Python
10个最常见的HTML5面试题 附答案
2016/06/06 HTML / CSS
美国羊皮公司:Overland
2018/01/15 全球购物
瑞典的玛丽小姐:Miss Mary of Sweden
2019/02/13 全球购物
皇家阿尔伯特瓷器美国官网:Royal Albert美国
2020/02/16 全球购物
局域网定义和特性
2016/01/23 面试题
写给女朋友的道歉信
2014/01/12 职场文书
安全检查管理制度
2014/02/02 职场文书
商铺消防安全责任书
2014/07/29 职场文书
镇政府副镇长群众路线专题民主生活会对照检查材料
2014/09/19 职场文书
班主任师德师风自我剖析材料
2014/10/02 职场文书
学校感恩节活动策划方案
2014/10/06 职场文书
2015年感恩父亲节演讲稿
2015/03/19 职场文书
演讲比赛通讯稿
2015/07/18 职场文书
聊聊Python中关于a=[[]]*3的反思
2021/06/02 Python
nginx搭建NFS网络文件系统
2022/04/14 Servers