You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// lib/module.js// ... Module.prototype.require=function(path){assert(path,'missing path');assert(typeofpath==='string','path must be a string');returnModule._load(path,this,false);};
在 Node.js 中,要说如果有几乎会在每一个文件都要用到的一个全局函数和一个全局对象,那应该是非
require
和module.exports
莫属了。它们是 Node.js 模块机制的基石。大家在使用它们享受模块化的好处时,有时也不禁好奇:global
对象下访问不到它们?require
一个目录时,Node.js 是如何替我们找到具体该执行的文件的?让我们从 Node.js 项目的
lib/module.js
中的代码里,细细看一番,一个文件被require
后,具体发生的故事,从而来解答上面这些问题。一个文件被
require
后所发生的故事当我们在命令行中敲下:
之后,
src/node.cc
中的node::LoadEnvironment
函数会被调用,在该函数内则会接着调用src/node.js
中的代码,并执行startup
函数:所以,最后会执行到
Module._load(process.argv[1], null, true);
这条语句来加载模块,不过其实,这个Module._load
在require
函数的代码中也会被调用:所以说,当我们在命令行中敲下
node ./index.js
,某种意义上,可以说随后 Node.js 的表现即为立刻进行一次require
, 即:随后的步骤就是
require
一个普通模块了,让我们继续往下看,Module._load
方法做的第一件事,便是调用内部方法Module._resolveFilename
,而该内部方法在进行了一些参数预处理后,最终会调用Module._findPath
方法,来得到需被导入模块的完整路径,让我们从代码中来总结出它的路径分析规则:代码中的条件判断十分清晰,让我们来总结一下:
/
结尾,则先检查该路径是否真实存在:package.json
中main
属性所指向的文件路径。.js
,.json
和.node
后缀,判断是否存在,若存在则返回加上后缀后的路径。index.js
,index.json
和index.node
,判断是否存在,若存在则返回拼接后的路径。.js
,.json
和.node
后缀,判断是否存在,若存在则返回加上后缀后的路径。/
结尾,则尝试读取该目录下的package.json
中main
属性所指向的文件路径。.js
,.json
和.node
后缀,判断是否存在,若存在则返回加上后缀后的路径。index.js
,index.json
和index.node
,判断是否存在,若存在则返回拼接后的路径。index.js
,index.json
和index.node
,判断是否存在,若存在则返回拼接后的路径。在取得了模块的完整路径后,便该是执行模块了,我们以执行
.js
后缀的 JavaScript 模块为例。首先 Node.js 会通过fs.readFileSync
方法,以 UTF-8 的格式,将 JavaScript 代码以字符串的形式读出,传递给内部方法module._compile
,在这个内部方法里,则会调用NativeModule.wrap
方法,将我们的模块代码包裹在一个函数中:所以,这便解答了我们之前提出的,在
global
对象下取不到它们的问题,因为它们是以包裹在外的函数的参数的形式传递进来的。所以顺便提一句,我们平常在文件的顶上写的use strict
,其实最终声明的并不是script-level
的严格模式,而都是function-level
的严格模式。最后一步, Node.js 会使用
vm.runInThisContext
执行这个拼接完毕的字符串,取得一个 JavaScript 函数,最后带着对应的对象参数执行它们,并将赋值在module.exports
上的对象返回:至此,一个同步的
require
操作便圆满结束啦。循环依赖
通过上文我们已经可以知道,在
Module._load
内部方法里 Node.js 在加载模块之前,首先就会把传模块内的module
对象的引用给缓存起来(此时它的exports
属性还是一个空对象),然后执行模块内代码,在这个过程中渐渐为module.exports
对象附上该有的属性。所以当 Node.js 这么做时,出现循环依赖的时候,仅仅只会让循环依赖点取到中间值,而不会让require
死循环卡住。一个经典的例子:执行
node main.js
,打印:最后
由于 Node.js 中的模块导入和 ES6 规范中的不同,它的导入过程是同步的。所以实现起来会方便许多,代码量同样也不多。十分推荐大家阅读一下完整的实现。
参考:
The text was updated successfully, but these errors were encountered: