大家好,我是前端西瓜哥。今天我们就来看看Node.js模块搜索的原理。模块种类模块有三种来源。核心模块:内置于Node.js中的包。如http、fs、路径。自定义模块:NPM包。比如axios和express都位于node_modules目录下的同名目录下,通过package.json的main字段指定入口文件。文件模块:项目本身的模块文件,使用路径表示法。包括相对路径(如“./utils”)和绝对路径(如“/Users/xigua/project/utils”)。需要注意的是,“a/b”不属于路径写法,它属于前两种,比如“fs/promises”和“@babel/core”。这是一个例子:consthttp=require('http');//Node.js内置包const{defaultContent}=require('./default');//开发者自己写的模块文件http.createServer((req,res)=>{res.writeHead(200,{'Content-Type':'text/plain'});res.end(defaultContent);}).听(3200);模块查找我们使用require()方法,传入一个字符串标识符,模块查找之旅就开始了。核心模块首先分析标识符的样式。如果不是路径的写法,我们会先检查Node.js自带的包是否匹配。如果匹配,则导入相应的模块,如require('http')得到一个http对象,可用于创建web服务等功能。如果npm包不匹配,会在当前文件目录下查找node_modules目录,看里面有没有对应的包。如果找不到,则继续查找父目录,直到根目录。如果找不到,会报Cannotfindmodule'packagename'错误。Files模块包通常是一个文件夹,里面有一个package.json文件,Node.js会将main字段对应的文件提取为模块文件。如果没有,依次搜索该目录下的index.js、index.json、index.node文件。可以通过module.paths变量获取要搜索的目录。如果你熟悉JavaScript的原型链,你会发现它们非常相似,可以打个比方来加深理解。如果标识是路径,则通过计算得到一个绝对路径,然后找到一个目录,与上面查找npm包的逻辑相同。如果找不到,就加上后缀再找。后缀依次添加:.js、.json、.node,找到后立即返回。如果一个文件没有后缀但是匹配上了,就会被当作一个js文件。上面没有提到缓存的情况。事实上,我们将缓存模块。让我在下面详细解释。模块缓存无论何时加载模块,都会缓存该模块。您可以在任何文件中输入缓存的内容。它是一个哈希表。键是确保缓存命中的模块的绝对路径,值是模块对象。const{模块}=require("模块");console.log(Module._cache);也可以通过require.cache变量获取,它指向与Module._cache相同的对象。Node.js内置的模块也需要缓存,但是不会记录在Module._cache中,而是保存在Module._cache中。下面是一个例子,index.js导入a.js,a.js下引入lodash.get包和模块缓存结果为:因为缓存的存在,一个模块文件只会执行一次,然后module.exports将被缓存。多次导入后,这个模块文件不会再执行,而是直接取出对应的module.exports。综上,我画了一个流程图,丢失了一些细节(路径位于目录后的逻辑)。
