💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
因为标识符有几种形式,对于不同的标识符,模块的查找和定位有不同程度上的差异。 ## 1.模块标识符分析 前面提到过,require()方法接受一个标识符作为参数。在Node实现中,正是基于这样一个标识符进行模块查找的。模块标识符在Node中主要分为以下几类: * 核心模块,如http、fs、path等。 * . 或 .. 开始的相对路径文件模块。 * 以 / 开始的绝对路径文件模块。 * 非路径形式的文件模块,如自定义的connect模块。 ### 核心模块 核心模块的优先级仅次于缓存加载,它在Node的源代码编译过程中已编译为二进制代码,其加载过程最快。 如果试图加载一个与核心模块标识符相同的自定义模块,那是不会成功的。如果自己编写了一个 http 用户模块,要想加载成功,必须选择一个不同的标识符或者换用路径的方式。 ### 路径形式的文件模块 以 . 、..和 / 开始的标识符,这里都被当作文件模块来处理。在分析路径模块时,require() 方法会将路径转换为真实路径,并以真实路径作为索引,将编译执行后的结果存放到缓存中,以使二次加载时更快。 由于文件模块给Node指明了确切的文件位置,所以在查找过程中可以节约大量时间,其加载速度慢于核心模块。 ### 自定义模块 自定义模块指的是非核心模块,也不是路径形式的标识符。它是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块的查找是最费时的,也是所有方式中最慢的一种。 在介绍自定义模块的查找方式之前,需要先介绍一下模块路径这个概念。 模块路径是Node在定位文件模块的具体文件时制定的查找策略,具体表现为一个路径组成的数组。关于这个路径的生成规则,我们可以手动尝试一番。 (1). 创建 `module_path.js` 文件,其内容为 `console.log(module.paths);` 。 (2). 将其放入任意一个目录中然后执行 node module_path.js 。 在Linux下,你可能得到这样一个数组输出: ~~~ [ '/home/jackson/research/node_modules', '/home/jackson/node_modules', '/home/node_modules', '/node_modules' ] ~~~ 而在Windows下,也许是这样: ~~~ [ 'C:\\Users\\Simon\\Desktop\\NodeDemo\\node_modules', 'C:\\Users\\Simon\\Desktop\\node_modules', 'C:\\Users\\Simon\\node_modules', 'C:\\Users\\node_modules', 'C:\\node_modules' ] ~~~ 可以看出,模块路径的生成规则如下: * 当前文件目录下的node_modules目录 * 父目录下的node_modules目录 * 父目录的父目录下的node_modules目录 * 沿路径向上逐级递归,直到根目录下的node_modules目录 它的生成方式与JavaScript的原型链或作用域链的查找方式十分类似。在加载的过程中,Node会逐个尝试模块路径中的路径,直到找到目标文件为止。可以看出,当前文件的路径越深,模块查找耗时越多,这是自定义模块的加载速度最缓慢的原因。 ## 2.文件定位 从缓存加载的优化策略使得二次引入时不需要路径分析、文件定位和编译执行的过程,大大提高了再次加载模块时的效率。 但在文件定位的过程中,还有一些细节需要注意,这主要包括文件扩展名的分析、目录和包的处理。 ### 文件扩展名分析 require()在分析标识符的过程中,会出现标识符中不包含文件扩展名的情况。CommonJS模块规范也允许在标识符中不包含文件扩展名,这种情况下,Node会按.js、.json、.node的次序补足扩展名,依次尝试。 在尝试的过程中,需要调用fs模块同步阻塞式的判断文件是否存在。因为Node是单线程的,所以这里是一个会引起性能问题的地方。小诀窍是:如果是.node或者.json文件,在传递给require()方法的标识符中带上扩展名,会加快一点速度。另一个诀窍是:同步配合缓存,可以大幅度缓解Node单线程中阻塞式调用的缺陷。 ### 目录分析和包 在分析标识符的过程中,require()通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时会经常出现,此时Node会将目录当作一个包来处理。 在这个过程中,Node对CommonJS包规范进行了一定程度的支持。首先,Node在当前目录下查找 package.json(CommonJS包规范定义的包描述文件),通过JSON.parse()解析出包描述对象,从中取出main属性指定的文件名错误,或者压根没有package.json文件,Node会将index当作默认文件名,然后依次查找index.js、index.json、index.node 。 如果在目录分析的过程中没有定位成功任何文件,则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕,依然没有找到任何目标文件,则会抛出查找失败的异常。