未完,更新中
JavaScript的思想是一切皆对象,而Node.js编程是基于模块化思想的。
1. 基本概念
Node.js采用了CommonJS模块系统,每个文件都是一个独立的模块,每个模块都有自己的作用域,可以有自己的变量和函数。这种模块化的设计使得代码更易于组织、维护和重用。
通过模块化,开发者可以将复杂的程序拆分成小的、独立的模块,每个模块专注于特定的功能。这样做有助于降低代码的耦合度,提高代码的可读性和可维护性。
此外,Node.js的模块化设计还支持模块之间的依赖管理,使得开发者可以方便地引入其他模块提供的功能。
2. 模块实现
模块代码默认是私有的,不会污染全局作用域
使用module.exports
或者 exports
导出模块中变量、函数或对象
示例:
1 | // hello.js |
上面代码定义了 sayHello
函数,并通过 module.exports
暴露接口,使得其它文件可以引入并调用
1 | // app.js |
使用时使用require
引入模块,并赋值给 hello
,此时 hello = sayHello
3. 共享引用机制
Node.js模块导出的是对象的引用,而不是对象实例,其它模块引入后,实际获取的是同一个对象的引用,这意味着任何修改都会影响到引入该对象的其它模块
1 | // module.js |
1 | // app.js |
打印结果可以看出 infoA
和infoB
两个对象引用的是同一个对象,Node.js这种共享引用机制,一是可以方便模块之间数据共享,二是有利于节省内存
如果导出的是字符串或数值,则不是引用,而是复制了副本
1 | // module.js |
1 | let { name, age } = require('./module') |
4. 路径解析
- 绝对路径解析
当给require()
函数传递以斜杆/
开头的路径,node.js会解析为绝对路径,即相对于文件系统根目录,比如require('/path/demo/module')
- 相对路径解析
当给require()
函数传递以斜杆./
或../
开头的路径,node.js会解析为相对路径,即相对于当前模块文件的路径,比如require('./module')
- 核心模块解析
当给require()
函数传递不是绝对路径也不是相对路径时,node.js会解析成核心模块,比如require('http')
- 模块路径解析
当在本层node_modules
目录查找不到核心模块,node.js会逐级往上查找node_modules
目录下是否有匹配模块 - 文件扩展名解析
当给require()
传递 路径不带扩展名,node.js会尝试添加扩展名匹配具体文件,顺序为.js > .json > .node
,如果还找不到文件,则抛出MODULE_NOT_FOUND
异常
5. 模块缓存
缓存机制
node.js中为了减少多次加载相同模块的性能开销,会缓存已加载的模块。
当加载模块时,会先检查缓存,已存在则直接换回缓存中的模块,没有才会重新加载,并进行缓存缓存清除
加载模块的缓存,存储到require.cache
对象,键值为模块绝对路径,通过delete
来删除
例如delete require.cache[require.resolve('./hello')]
缓存更新
对于已经缓存的模块,如果模块文件有更新,缓存并不会更新,只有当再次调用require()
加载时,缓存才会更新
6. 循环依赖
循环依赖通常是开发中需要避免的,因为它会增加代码的复杂性,并导致难以维护的代码。
Node.js 模块的加载是同步的,因此循环依赖会导致其中一个模块在加载过程中尝试去加载另一个模块,从而导致死锁或加载错误。
Node.js模块系统检测到循环依赖的情况后,为了避免无限递归,加载中的模块会先返回不完整的exports对象,仅包含已经执行的部分模块内容,未执行的部分在后面继续执行。
通常需要将相互依赖的模块,拆分成更小的模块,以消除循环依赖,另一种方法是使用延迟加载。
循环依赖在《Node.js设计模式》有详细解释,有兴趣可以看看。