什么是模块?
- 每个.js文件就是一个模块
- 从npm上下载的一个包(可能是由多个文件组成的一个实现特定功能的包)也是一个模块
- 任何文件或目录只要可以被Node.js通过
require()
函数加载的都是模块 - 每个模块就是一个独立的作用域,模块和模块之间不会互相"污染"
- 我们可以通过编程的方式,指定某个模块要对外暴露的内容(其实就是指定require的返回值,通过require的返回值对外暴露指定内容)。这个对外暴露内容的过程也叫"导出"
module.exports
为什么要进行模块化
- 方便代码管理、项目维护
- 有助于分工协同开发
- 模块和模块之间不会出现变量"污染",一个模块就是一个作用域。
- 模块化可以做到职责分离,每个模块实现一个独立的功能
补充:面向对象编程的5(6)大原则:
- 开放封闭原则
- 里氏替换原则
- 依赖倒置原则
- 单一职责原则
- 接口隔离原则
什么是包?
- 通过package.json描述的一个文件或目录(可以理解成一个实现某个功能的1个文件或多个文件,通过package.json组织起来)
- 包不一定能被Node.js通过
require()
来加载,那么就不就叫模块。比如有些包中没有设置启动文件(package.json中的main字段),就不是模块。 - package 和 module 参考链接
在Node.js中的模块主要分为:核心模块 和 文件模块
核心模块
- http、fs、path、url、net、os、readline、......
- 核心模块在Node.js自身源码编译时,已经编译成二进制文件
- 部分核心模块在Node.js进程启动的时候已经默认加载到缓存里面了
文件模块(包含独立文件模块和第三方模块)
- 文件模块可以是:.js 模块、.node模块、*.json模块,这些都是文件模块
- 无论从npm上下载的第三方模块还是我们自己编写的模块都是文件模块
module.exports 和 exports
- 在每个模块中module表示当前模块对象, 里面保存了当前模块对象的各种信息
- module.exports 其实就是 require()加载模块时的返回值
- exports 就是module.exports的一个引用
exports = module.exports;
- 特别注意:最终暴露给require的返回值的是:module.exports, 而不是exports
// To illustrate(说明) the behavior, imagine this hypothetical implementation of require(), which is quite similar to what is actually done by require():
function require(...) {
var module = { exports: {} };
((module, exports) => {
// Your module code here. In this example, define a function.
function some_func() {};
exports = some_func;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = some_func;
// At this point, the module will now export some_func, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
require 加载模块时做了2件事
- 执行了模块中的代码
- 返回了模块中对外暴露的内容(可能是对象、函数等等)
二、require 加载模块顺序
看 require() 加载模块时传入的参数是否以 './' 或 '../' 或 '/' 等等这样的路径方式开头(相对路径或绝对路径都可以)
是,那么会按照传入的路径直接去查询对应的模块。
- 传入的是否为具体的文件名
-
require('./test.js') 是具体的文件名
- 直接根据给定的路径去加载模块,找到了加载成功,找不到加载失败
-
require('./test'); 不是具体的文件名、
- 第一步:根据给定的路径,依次添加文件后缀 .js、.json、.node进行匹配,如果找不到匹配执行第二步
- 第二步:查找是否有 test 目录(尝试找 test 包)
- 找不到:加载失败
- 找到了:依次在 test 目录下查找 package.json 文件(找到该文件后尝试找 main 字段中的入口文件)、index.js、index.json、index.node,找不到则加载失败
-
- 不是,那么就认为传入的是 "模块名称"(比如:require('http')、require('mime'))
- 是核心模块:直接加载核心模块
- 不是核心模块
- 依次递归查找 node_modules 目录中是否有相应的包
- 从当前目录开始,依次递归查找所有父目录下的 node_modules 目录中是否包含相应的包
- 如果查找完毕磁盘根目录依然没有则加载失败
- 打印输入 module.paths 查看
- 依次递归查找 node_modules 目录中是否有相应的包
// require('http')
// require('mime')
// 情况一:require() 的参数是一个路径
require('./index2.js')
// index2.js
// index2.json
// index2.node
// index2 文件夹 -> package.json -> main(入口文件 app.js -> index.js/index.json/index.node) -> 加载失败
require('ndex2')
// 情况二: require() 的参数不是路径,直接就是一个模块名称
// 1. 先在核心模块中查找,是否有和给定的名字一样的模块。如果有,则直接加载该核心模块。
// require('http')
// 2. 如果核心模块中没有该模块那么就会认为这个模块是一个第三方模块(自定义模块)
// 先会去当前js文件所在的目录下去找是否一个一个 node_modules 文件夹
// require('mime')
require 加载模块注意点
- 所有模块第一次加载完毕后都会有 缓存,二次加载直接读取缓存,避免了二次开销
- 因为有 缓存,所以模块中的代码只在第一次加载的时候执行一次
每次加载模块的时候都优先从缓存中加载,缓存中没有的情况下才会按照 node.js 加载模块的规则去查找
核心模块在 Node.js 源码编译的时候,都已经编译为二进制执行文件,所以加载速度较快(核心模块加载的优先级仅次于 缓存加载)
核心模块都保存在 lib 目录下
试图加载一个和 核心模块 同名的 自定义模块(第三方模块)是不会成功的
- 自定义模块要么名字不要与核心模块同名
- 要么使用路径的方式加载
- 核心模块 只能通过 模块名称 来加载(错误示例:require('./http'); 这样是无法加载 核心模块 http的 )
- require() 加载模块使用 ./ 相对路径时,相对路径是相对当前模块,不受执行 node 命令的路径影响
- 建议加载文件模块的时候始终添加文件后缀名,不要省略。