上周过了一遍REPL,那接下去应该看看哪部分呢?既然知道怎么玩输入输出了,那就要开始了解模块部分的内容了。
Node.js
有一个简单的模块加载系统,文件和模块一一对应。
foo.js
1
2const circle = require('./circle.js')
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`)
circle.js
1
2
3
4
5const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r
官方这个例子,circle.js
暴露了area
和circumference
的方法,然后在foo.js
被引入。
通过export
关键字指定附加属性,把模块内的函数和对象暴露到根模块中。
而本地变量PI
是circle.js
的私有变量。类似这样的变量就会通过module wraper
包裹起来,不被其他模块访问到。
export
暴露接口,然后通过require('module')
方式引用其他模块的函数和对象。
主入口
在node里直接执行一个文件,如$ node foo.js
,require.main
将会被设为当前的这个入口文件模块。这一点可以通过require.main === module
来测试。
但如果是通过require('./foo.js')
这种方式来引入的话,这个require.main
则不会指向foo.js
。
module
提供了filename
属性,如果在当前REPL的环境中,我们可以通过require.main.filename
获取到当前应用的入口文件名。
例如在REPL中我们建立的myeval.js里
1 | $ node myeval.js |
这里就能看到我对应执行的入口文件了。
包管理
文档这部分涉及到一些之前版本的包依赖管理的问题,例如引入foo
模块的时候,我们可能需要引入bar
模块,因为它被foo
所依赖,而且有可能bar
自身还依赖了其他模块,还有可能在这个子模块的依赖中的某个模块还依赖了不同版本的同一个模块,造成了冲突,甚至还依赖了foo
模块,形成一个循环依赖。
都说node_module
深不见底,比黑洞还深,这就是其中的缘由了。
不过这点随着Node.js
引入了realpath
这个概念之后就被简单的解决了,它会查找实际的路径,解析连接。具体的此处暂不深究。
require.resolve()
我们在使用require()
获取X确切的文件名的时候,会用到require.resolve()
这个函数。那它都做了什么呢?官方伪代码如下,逻辑关系已经展示的很清晰。
1 | require(X) from module at path Y |
看完上面这个伪代码之后,我们也就大概知道了上面的那个require.main.paths
里的那串到底咋来的了。
核心模块总会被优先加载。这个其实从上面的部分伪代码中也能看出来。
循环依赖,即便产生了循环依赖,那其实依然还是有依赖的先后情况的,就根据这个先后顺序,当依赖了未完成加载的,那就先先暴露
unfinished copy
,然后继续往下走,直到这个模块完成加载,以此类推,最后到主模块的时候这些子依赖肯定是完成了最终的加载了的。这里有点绕。
文件模块
文件夹模块
从
node_modules
文件夹加载模块
在'/home/ry/projects/foo.js' 调用 require('bar.js')
然后查找顺序会是这样的
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
从全局文件夹中加载模块,更推荐local的
node_module
而不是全局,因为涉及到一个版本依赖的问题。没法保证全局的版本是否符合当前的需求。
模块包装
在模块代码被执行之前,`Node.js`将会把这个模块用下面的形式包装起来
1 | (function(exports, require, module, __filename, __dirname) { |
这么做是为了限定变量作用范围,通过`__filename`和`__dirname`就能取到文件的绝对文件名和目录路径。
模块的作用范围
- dirname 和 filename,在目录
/usr/mjr
下执行$ node example.js
1 | console.log(__dirname); |
其实看完上面那段伪代码,基本下面的也都是些补充说明了。
官方文档确实很清晰明了,也让我明白了之前没想到的一些地方,之前只管用,没去想后面的实现逻辑。
所以我的思维还是有待提升的,需要学习的还有很多很多呀。
不能光去用,还得去想想为什么这么实现。
思考,才能让我进步更快。