(一)简单模块的定义和使用
定义一个计算圆形的面积和周长的模块,让我们开启node之旅
将这个文件存为circle.js,并新建一个app.js文件,并写入以下代码:
可以看到模块的调用非常的方便,只需要require 需要调用的文件即可
(二)模块载入策略(重点)
Node.js的模块分为两类
- 原生(核心)模块
- 文件模块
原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。其中原生模块都被定义在lib这个目录下面,文件模块则不定性。
由于通过命令行加载启动的文件几乎都为文件模块。我们从Node.js如何加载文件模块开始谈起。加载文件模块的工作,主要由原生module来实现和完成,该原生模块在启动是已经被加载,进程直接调用到runMain静态方法。
_load静态方法在分析文件名之后执行
并根据文件路径缓存当前模块对象,该模块实例对象则根据文件名加载。
实际上在文件模块中,又分为3类模块。这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法。
- .js 通过fs模块同步读取js文件并编译执行。
- .node 通过C/C++ 进行编写的Addon。 通过dlopen 方法进行加载。
- .json 读取文件,调用JSON.parse解析加载。
这里我们将详细描述js后缀的编译过程。Node.js在编译js文件的过程中实际完成的步骤有对js文件内容进行头尾包装。以app.js为例,包装之后的app.js将会变成以下形式。
这段代码会通过VM 原生模块 的runlnThisContext方法执行(类似eval,只是具有明确上下文,不污染全局),返回一个具体的function对象。最后传入module对象的exports,require方法,module,文件名,目录名作为实参并执行。
这就是为什么require并没有定义在app.js文件中,但是这个方法却存在的原因!!!
从Node.js的API文档中可以看到还有 __filename, __dirname, module , exports 几个没有定义但是却存在的变量。其中 __filename 和 __dirname在查找文件路径的过程中分析得到后传入的。 module变量是这个模块对象自身,exports是在module的构造函数中初始化的一个空对象({},而不是null)
在这个主文件中,可以通过require方法去引入其余的模块。而其实这个require方法实际调用的就是load方法。
load方法在载入,编译,缓存了module后,返回module的exports对象。这就是circle.js文件中只有定义在exports对象上的方法才能被外部调用的原因。
以上所描述的模块载入机制均定义在lib/module.js中。
(三)require方法中的文件查找策略
由于Node.js中存在4类模块(原生模块和3中文件模块),尽管require方法及其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。
安装yarn
npm install -g yarn --registry=https://registry.npm.taobao.org
进军西南!!!