美文网首页程序员今日看点@IT·互联网
深入理解node中require的原理及执行过程

深入理解node中require的原理及执行过程

作者: 游泳的石头 | 来源:发表于2016-10-26 00:35 被阅读5311次

前言


在朴灵老师的《深入浅出nodejs》一书中提到,每个模块文件的require,exports和module这3个变量并没有在模块中定义,也并非全局函数/对象。而是在编译的时候Node对js文件内容进行了头尾的包装。在头部加了(function (exports, require, module, __filename, __dirname) {,在尾部加了 \n});。这样看起来虽然貌似理解了require,exports和module的来源。但是依然不明白在这个函数外调用时,比如require时,发生了什么。于是花了点时间去仔细阅读了一下node的源代码,感觉豁然开朗,这里把我的分析过程记录一下,仅供大家参考。

源文件分析


有关require的内容,只要阅读两个文件就够了。分别是lib/internal/bootstrap_node.jslib/module.js

1、require入口: 

lib/module.js

可以看到,这里继续调用了_load方法用于加载文件

2、Module._load方法:

lib/module.js

可以看到,这里继续调用了_resolveFilename方法,顾名思义,这应该是一个解析需要require的文件名的方法

3、Module._resolveFilename方法:

lib/module.js

可以看到,这里又继续调用了_findPath方法

4、Module._findPath方法:

lib/module.js lib/module.js

可以看到,这里完整的显示了node是如何根据require传入的名称来定位具体的文件的,他们的顺序依次是:

1、先从缓存中读取,如果没有则继续往下

2、判断需要模块路径是否以/结尾,如果不是,则要判断

    a. 检查是否是一个文件,如果是,则转换为真实路径

    b. 否则如果是一个目录,则调用tryPackage方法读取该目录下的package.json文件,把里面的main属性设置为filename

    c. 如果没有读到路径上的文件,则通过tryExtensions尝试在该路径后依次加上.js,.json和.node后缀,判断是否存在,若存在则返回加上后缀后的路径

3、如果依然不存在,则同样调用tryPackage方法读取该目录下的package.json文件,把里面的main属性设置为filename

4、如果依然不存在,则尝试在该路径后依次加上index.js,index.json和index.node,判断是否存在,若存在则返回拼接后的路径。

5、若解析成功,则把解析得到的文件名cache起来,下次require就不用再次解析了,否则若解析失败,则返回false

5、文件名成功获取后,再次回到Module._load方法:

lib/module.js

可以看到,这里继续调用了load方法来加载文件

6、Module.load方法:

lib/module.js

可以看到,这里将根据不同的文件类型(js,json和node),采用不同的加载方法

7、不同文件类型的加载方法不同:

lib/module.js

可以看到,js文件将在读入文件(同步读)内容后进行编译,json文件则用JSON.parse解析内容,node文件则使用dlopen进行动态链接库载入

8、这里仅针对通常的js类型的文件的载入进行分析:

lib/module.js

可以看到,js文件内容先被wrap(包装)了一下,然后才使用runInThisContext来运行包装后的代码,而传入的参数就是前面说的exports, require, module,还有当前文件名及所在目录名。此外,也看到,模块中的this其实是指向module的exports,而不是global!

9、Module.wrap:

可以看到,Module.wrap只是NativeModule.wrap的引用,这里的NativeModule则位于lib/internal/bootstrap_node.js中

10、NativeModule.wrap:

lib/internal/bootstrap_node.js

可以看到,这里就对上了文章开头所说的编译时node文件内容的头尾包装,自此,本次源码分析结果。

总结


从上面的分析可以看出来,exports其实是module的属性,require则是Module原型的方法。exports.xx=xx,其实跟module.exports.xx=xx其实是一样的,不过如果直接为export赋值,则不能写成exports=xx,而应该写成module.exports=xx,因为exports在这里只是一个引用。

从上面也可以看到,每一次require,都会把new一个Module,并且把这个Module添加到当前模块的children中,并且返回新建的Module对象的exports。

其实node启动的原理跟require是一样的,src/node.cc中的node::LoadEnvironment函数会被调用,在该函数内则会接着调用lib/internal/bootstrap_node.js中的代码,并执行startup函数,startup函数会执行Module.runMain方法,而Module.runMain方法会执行Module._load方法,参数就是命令行的第一个参数(比如: node ./app.js),如此,跟上面require就走到一起了。

lib/module.js

要想深入的理解一件事情的原理,还是需要仔细的阅读和研究底层的实现代码,好在node关于require实现原理方面的代码还挺简单的,想要深入理解node的同学还是很有必要仔细读一下的。

相关文章

  • 深入理解node中require的原理及执行过程

    前言 在朴灵老师的《深入浅出nodejs》一书中提到,每个模块文件的require,exports和module这...

  • PHP相关

    一、 原理、流程相关 Nginx解析PHP过程 PHP执行过程 PHP垃圾回收机制 深入理解PHP Opcode缓...

  • 模块化的简单化(杂记)

    本文首先针对于有点node基础和js基础的同学,阅读完本文大致理解require的原理。 Node模块化解决的问题...

  • require加载机制及加载规则

    在Node中引入( require )模块,经过如下几个步骤: 路径分析 文件定位 编译执行(加载执行:已编译进了...

  • 深入理解iOS App的启动过程

    前言 参考资料:深入理解iOS App的启动过程iOS 应用程序启动过程及原理总结iOS:App启动过程详解(不同...

  • NodeJS基础

    在node中,一个js文件就是一个模块; 在node中,通过require()函数来引入外部模块;require(...

  • MapReduce的原理及执行过程

    MapReduce简介 MapReduce是一种分布式计算模型,是Google提出的,主要用于搜索领域,解决海量数...

  • Node模板引擎使用

    require 加载 文件模块 并执行里面的代码-我们在引用Node中的核心模块是都要使用var fs = req...

  • node学习笔记

    1.执行node文件 node 文件名.js 2.读取文件 1),使用require方法加载fs核心模块 var ...

  • AsyncTask的使用及原理解析

    AsyncTask的使用及原理解析 Android应用在主线程中执行耗时任务时会导致ANR(Application...

网友评论

    本文标题:深入理解node中require的原理及执行过程

    本文链接:https://www.haomeiwen.com/subject/kmhduttx.html