再谈Node.js的模块加载方式+机制与运行原理
Author:zhizunbao Date:
此篇主要由以下几篇神文凝练而成
相对路径指定模块,一般用于加载自己的模块。
必须用到的符号:
./ 表示当前目录,相对路径所相对的就是当前的目录
../ 表示上一级模块,可以无限使用直到跳转到根目录
nodejs的模块分类
核心模块:包含在 Node.js 源码中,被编译进 Node.js 可执行二进制文件 JavaScript 模块,也叫 native 模块,比如常用的 http, fs 等等
C/C++ 模块,也叫 built-in 模块,一般我们不直接调用,而是在 native module 中调用,然后我们再 require
native原生模块:http fs path等,这些模块都在源码包的lib目录下面,nodejs安装好之后是找不到这些模块的,都作为node.exe的一部分了,require这些模块永远没问题的,如果哪天出现问题了,直接重启电脑或者重装node。有什么疑问可以通过下载源码对这些原生模块的功能进行查看。地址:https://nodejs.org/download/。
第三方模块-文件模块:非 Node.js 源码自带的模块都可以统称第三方模块,比如 express,webpack 等等。
JavaScript 模块,这是最常见的,我们开发的时候一般都写的是 JavaScript 模块
JSON 模块,这个很简单,就是一个 JSON 文件
C/C++ 扩展模块,使用 C/C++ 编写,编译之后后缀名为 .node
自定义模块:我们自己写的模块,之所以独立出来是因为其加载和另两种模块有区别。
怎样定义模块
nodejs声明一个模块有2中做法
exports.module_name
module.exports
关于这两个的区别也很简单,不过要讲明白很费劲,关键点在于知道有 module 这个全局变量的存在,打印出来并做几次尝试,就完全明白了,这里有一篇非常精彩 的关于这两者异同的文章:《nodejs中exports与module.exports的实践》
第三方模块安装在哪(NPM)
几条命令
npm config get/set prefix //查看设置全局安装目录,全局安装的模块就安装该目录下面的node_modules目录下 npm install [-g] // -g 全局安装,模块将会安装到全局目录下。不带 -g 则直接安装在当前所在目录下,即为本地安装
模块的存在形式
1、文件包含,这个比较直观,直接指定到文件名(去掉 .js 后缀),就可以得到文件里面所有导出的模块。
2、文件夹包含,通过npm安装的第三方模块都是这种方式,指定到模块所在的文件夹,该文件夹就是模块名,以express为例:
模块加载机制:
首先搜索当前目录下的 package.json 文件,查找里面的mian属性,如果存在,则加载该属性所指定的的文件。如果不存在 package.json 或者该文件里面没有main字段,nodejs将试图加载 index.js
都不存在那么就只有说一声Cannot find module了。
node模块的载入及缓存机制
载入内置模块(A Core Module)
载入文件模块(A File Module)
载入文件目录模块(A Folder Module)
载入node_modules里的模块
自动缓存已载入模块
一、载入内置模块
Node的内置模块被编译为二进制形式,引用时直接使用名字而非文件路径。当第三方的模块和内置模块同名时,内置模块将覆盖第三方同名模块。因此命名时需要注意不要和内置模块同名。如获取一个http模块
var http = require('http')
返回的http即是实现了HTTP功能Node的内置模块。
二、载入文件模块
绝对路径的:var myMod = require('/home/base/my_mod')
相对路径的:var myMod = require('./my_mod')
注意,这里忽略了扩展名“.js”,以下是对等的
var myMod = require('./my_mod')
var myMod = require('./my_mod.js')
三、载入文件目录模块
可以直接require一个目录,假设有一个目录名为folder,如
var myMod = require('./folder')
此时,Node将搜索整个folder目录,Node会假设folder为一个包并试图找到包定义文件package.json。如果folder目录里没有包含package.json文件,Node会假设默认主文件为index.js,即会加载index.js。如果index.js也不存在,那么加载将失败。
+folder
index.js
modA.js
package.json
+init.js
package.json定义如下
{ "name": "pack", "main": "modA.js" }
此时 require('./folder') 将返回模块modA.js。如果package.json不存在,那么将返回模块index.js。如果index.js也不存在,那么将发生载入异常。
四、载入node_modules里的模块
如果模块名不是路径,也不是内置模块,Node将试图去当前目录的node_modules文件夹里搜索。如果当前目录的node_modules里没有找到,Node会从父目录的node_modules里搜索,这样递归下去直到根目录。
不必担心,npm命令可让我们很方便的去安装,卸载,更新node_modules目录。
五、自动缓存已载入模块
对于已加载的模块Node会缓存下来,而不必每次都重新搜索。下面是一个示例
modA.js
console.log('模块modA开始加载...') exports = function() { console.log('Hi') } console.log('模块modA加载完毕')
init.js
var mod1 = require('./modA') var mod2 = require('./modA') console.log(mod1 === mod2)
虽然require了两次,但modA.js仍然只执行了一次。mod1和mod2是相同的,即两个引用都指向了同一个模块对象。
模块在每一次加载之后都会被缓存起来。这也就意味着在每一次使用require(‘foo’)时,返回的都是同一个对象,即使文件随后被修改过。
多次执行require('./modA')并不会导致模块代码被执行多次,这是一个很重要的功能。
而如果你确实是想让一个模块的代码被执行多次,那么就导出一个函数,然后多次调用那个函数。
模块缓存注意事项
模块缓存基于它们的resolved filename。由于被调用的模块的位置[从node_modules文件夹中加载]不同,所以模块可能会被解析为不同的文件名。
如果解析的结果是不同的文件,但却总是返回相同的对象,这并不是我们所希望的结果。
此外,在大小写不敏感的文件系统或操作系统上,不同的文件名可能会指向相同的文件,但缓存系统依旧会将它们视为不同的模块,并多次加载文件。例如,require(‘./foo’) 和 require(‘./FOO’)不管./foo或./FOO是否为同一个文件,都将会返回两个不同的对象。
require('__')||import '__' 这里应该怎么填
相对路径指定模块,一般用于加载自己的模块。必须用到的符号:
./ 表示当前目录,相对路径所相对的就是当前的目录
../ 表示上一级模块,可以无限使用直到跳转到根目录
绝对路径指定模块地址,除了原生模块之外,任何文件模块都可以加载到,除非路径出错了。
比如我们可以这样子加载express模块 '/www/node_module/express/'
直接使用 require('xxx') 那么所加载的模块要么是原生模块,要么该模块在某个node_modules目录下面
混合模式
为了确定通过require()函数来加载时,调用的明确的文件名,我们可以使用
require.resolve()函数。
能将上述的方式搭配利用,需要的就是require.resolve方法体现出来的高水平算法。
文件的路径为Y,require(x):
如果X是核心模块
a. 返回核心模块
b. 结束代码如果 X 以 ‘./‘ 或 ‘/‘ 或 ‘../‘开始
a. 以文件的形式加载(Y + X)
b. 以目录的形式加载(Y + X)以NODE_MODULES的形式来加载(X, dirname(Y))
抛出异常 “not found”
以文件的形式加载的具体说明
1.如果存在文件x,那么就把x以javascript文本的形式来加载
1.如果存在文件x.js,那么就把x.js以javascript文本的形式来加载
3.如果存在文件x.json,
那么就解析 x.json 为一个 JavaScript 对象
4.如果存在文件x.node, 那么就把 x.node 作为一个二进制文件来加载
以目录的形式加载的具体说明
如果存在文件 X/package.json,
a. 解析 X/package.json, 然后寻在main字段.
b. let M = X + (json 的main字段)
c. 以文件的形式加载(M)如果存在文件X/index.js, 把 X/index.js 以javascript文本的形式来加载
如果存在文件X/index.json, 解析 X/index.json 为一个 JavaScript 对象
如果存在文件X/index.node, 把 X/index.node 作为一个二进制文件来加载
以NODE_MODULES的形式来加载(X, START)的具体说明
let DIRS=NODE_MODULES_PATHS(START)
for each DIR in DIRS:
a. 以文件的形式加载(DIR/X)
b. 以目录的形式加载(DIR/X)
NODE_MODULES_PATHS(START)函数的具体说明
let PARTS = path split(START)
let I = count of PARTS - 1
let DIRS = []
while I >= 0,
a. if PARTS[I] = “node_modules” CONTINUE
c. DIR = path join(PARTS[0 .. I] + “node_modules”)
b. DIRS = DIRS + DIR
c. let I = I - 1return DIRS
nodejs模块运行分析
假设有一个 index.js 文件,里面只有一行很简单的 console.log('hello world') 代码。当输入 node index.js 的时候,Node.js 是如何编译、运行这个文件的呢?
可以看到,主要是调用 Module._load 来加载执行 process.argv[1]。
下文我们在分析模块的 require 的时候,也会来到 lib/module.js 中,也会分析到 Module._load。因此我们可以看出,Node.js 启动一个文件的过程,其实到最后,也是 require 一个文件的过程,可以理解为是立即 require 一个文件。
分析 require 的原理
var http = require('http');
那么当执行这一句代码的时候,会发生什么呢?
先生成 cacheKey,判断相应 cache 是否存在,若存在直接返回
如果 path 的最后一个字符不是
/
:判断路径如果存在,直接返回
尝试在路径后面加上 .js, .json, .node 三种后缀名,判断是否存在,存在则返回
尝试在路径后面依次加上 index.js, index.json, index.node,判断是否存在,存在则返回
如果路径是一个文件并且存在,那么直接返回文件的路径
如果路径是一个目录,调用
tryPackage
函数去解析目录下的package.json
,然后取出其中的main
字段所写入的文件路径如果还不成功,直接对当前路径加上 .js, .json, .node 后缀名进行尝试
如果 path 的最后一个字符是
/
:调用
tryPackage
,解析流程和上面的情况类似如果不成功,尝试在路径后面依次加上 index.js, index.json, index.node,判断是否存在,存在则返回
这里目前还没有如何跟直白简练的文字来概括此文内容,所以,及不copy了
转载本站文章《再谈Node.js的模块加载方式+机制与运行原理》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/nodejs/121.html