Node.js Model 引入流程
Node model 可以相互进行调用,Node.js 为此提供了一个简单的模块系统,这可以内置的也可以是通过 npm
进行安装的,因此 Node.js 提供了 exports
和 require
两个对象,其中 exports
用于模块公开的接口,而 require
则用于获取模块的 exports
对象:
1 | function Hello() { |
通过 exports
对象将 Hello
对象本身,因此在通过 require
对象进行引入的时候,就会输出 Hello 函数。
1 | var Hello = require('./modelName') |
在 Node.js 模块中,模块主要分为 核心模块,这是 Node.js 所提供的模块,而另一个则是用户所编写的模块,被称之为 文件模块。这之间的区别就是核心模块部分在 Node 编译的过程中,同时编译了二进制的执行文件,所以在 Node 进程启动时,核心模块就会被加载到内存中,因此和因模块在引入的时候,文件定位以及编译执行就会被省略掉。
但用户所编写的文件模块则需要通过 查找路径,文件定位,编译执行 这三个流程,并在运行时进行动态加载,因此比较慢。所以和核心模块比起来,速度是自然赶不上人家内置的。
require 文件查找策略
文件缓存区
对于 require 的文件缓存区主要的作用就是会优先从文件模块中加载已经存在的模块这也就说明了 Node 对引入过的模块都会进行缓存,以减少第二次使用时的加载时间。
但与浏览器缓存静态脚本文件不同的是,Node.js 所缓存的是编译之后的对象,无论是核心模块还是文件模块,对于二次加载都一律采用缓存优先的方式进行。
不同之处在于核心模块的检查优于文件模块的缓存检查
原生模块加载
原生模块的加载仅次于检查文件缓存区是否存在,因此通过 require
解析完文件名之后,检查该模块是否在原生模块中。
如 http
模块,虽然会在项目目录下存在 http/http.js/http.node/http.json
文件,但 http
模块不会从这些文件中加载,而是从原生模块中进行加载。
因此原生模块内同样有也有一个缓存区,如缓存区没有被加载过,就会调用原生模块的加载方式进行加载和执行。
文件加载
当文件模块在缓存中并不存在是,而且还是原生模块的时候,require
方法所传入的参数就会被 Node.js 解析,并从文件系统中加载到实际的文件。对于没一个被加载的文件模块,创建这个模块对象的时候就会生成一个 paths
属性,也就是 /node_modules
,因此我们可以直接通过 console.log(module.paths)
来根据当前文件的路径计算得到 module path
。
1 | [ '/home/kunlun/Development/Web/Demo/node/node_modules', |
从当前文件目录内开始查找
node_modules
目录,然后依次进入查找父目录下的node_modules
,以此循环迭代到根目录下的node_modules
目录。
如果 ```require`` 指向的是绝对路径的文件,查找时就直接作为当前的路径进行使用,如果不是则进入查找的流程。以 module_path 中取出地一个目录作为查找的基准:
- 直接尝试从当前目录进行查找,如果存在则结束流程,反而继续进行查找,并进入下一阶段
- 尝试添加扩展名后缀进行查找,如果存在就结束查找,否则进入下一个阶段
- 尝试将 require 的参数作为一个包进行查找,读取项目目录下的
package.json
文件 - 尝试添加扩展名或查找文件,如果存在就结束流程,否则进入下一个阶段
- 如果继续失败,则取出 module path 中下一个目录作为查找的基准,循环 1~5 步骤
- 如果仍然失败,则一直循环 1~6 步骤,知道 module path 中最后一个值
- 如果持续失败,则抛出异常。