意外
意外发生在2021/6/21晚上,根据指定的包名,却无法正确找到指定的包.
简介
Node 引入模块,需要经历三个步骤:路径分析,文件定位,编译执行。—–《深入浅出Node.js》
Node 中的模块分为核心模块和文件模块。
(1) 核心模块在 Node 源码编译过程中,编译成为二进制文件,在 Node 启动阶段部分核心模块就被加载进内存,所以省去了文件定位和编译的时间,加载速度最快。
(2) 文件模块则是在运行时动态加载。
(3) 自定义模块是指非核心模块,也不是路径形式的文件模块。以文件或者包的形式存在,这类模块的查找是最费时的。
规则
模块路径:Node 在定位文件模块的时候采用的一种查找策略。具体表现为一个路径组成的数组。
可以在本地任意位置创建一个js脚本,在其中打印模块路径
console.log(module.paths)
如我在桌面创建脚本后运行后,脚本输出结果如下:
1 | [ |
由此可见,其路径寻址规则如下:从当前目录的 node_modules 中寻找 -> 父目录的 node_modules 中寻找 -> 递归一直到根目录的 node_modules。node寻址规则官方文档
它的生成方式与 JavaScript 原型链或者作用域链的查找方式十分类似。Node 会逐个尝试模块路径,直到找到模块或者查找到根目录位置。可以看出,当文件路径比较深的时候,模块查找会比较耗时。
Node 对引入过的模块都会进行缓存,无论是核心模块还是文件模块,require() 方法都采用缓存优先的方式进行加载,并且核心模块的优先级高于文件模块。
查找意外的元凶
从缓存加载的优化策略使得二次引入时不需要路径分析、问津定位和编译执行的过程,大大提高了再次加载模块时的效率,但是在文件定位的过程中,存在一些细节,主要包括文件扩展名的分析、目录和包的处理,意外产生的真正原因正是出现在这里。
- 文件扩展名分析
require()在分析标识符的过程中,会出现标识符中不包含文件扩展名的情况。CommonJS模块规范也允许在标识符中不包含文件的扩展名,这种情况下,Node会按.js、.json、.node的次序补足扩展名,依次尝试。
在尝试过程中,需要调用fs模块(node中,在RN中调用fs-extra)同步阻塞式的判断文件是否存在。因为Node是单线程,所以这里是一个会引起性能问题的地方。
Tips:
1.如果是.node和.json文件,在传递给require()的标识符中带上扩展名,会加快一点速度。2.配合缓存,可以缓解Node单线程中阻塞式调用的缺陷。
- 目录分析和包
在分析标识符的过程中,require()通过分析文件扩展名之后,可能没有找到对应的文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时会经常出现,此时Node会将目录当做一个包来处理。
在这个过程中,Node对CommonJS包规范进行了一定程度的支持。(重点来了..敲黑板)首先 Node在当前目录下查找package.json(CommonJS包规范定义的包描述文件),通过JSON.parse()解析出包描述对象,匹配到name属性相同的包,若存在多个name属性一样的package.json文件时,会抛出重复定义的错误,再从中取出main属性指定指定的文件路径进行定位。如果文件名缺少扩展名,将会进入扩展名分析步骤。
而如果main属性没有指定包入口文件,或者压根没有package.json文件,Node会将index当做默认文件名,然后依次查找index.js、index.node、index.json。
如果在目录分析的过程中没有定位成功任何文件,则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕(即按照递归依次向父级查询完毕),依然没有查找到目标文件,则会抛出查找失败的异常。
总结
分析完之后,可以知道这个意外发生的原因是由于NodeJS对CommonJS模块规范的支持,导致在目录分析时,会先去查找package.json文件导致的,谨记!
扫描二维码,分享此文章