模块化的基本概念
1、 什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
-
编程领域中的模块化
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并相互以赖的多个小模块。
把代码进行模块化拆分的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
-
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
- 使用什么样的语法格式来引用模块
- 在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
2、 Node.js 中模块的分类
-
Node.js 中模块的分类
Node.js中根据模块来源的不同,将模块分为了3大类,分别是:
- 内置模块(内置模块是由Node.js官方提供的,例如fs、path、http等)
- 自定义模块(用户创建的每个.js文件,都是自定义模块)
- 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
-
加载模块
使用强大的
require()方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:// 1. 加载内置的 fs 模块 const fs = require('fs') // 2. 加载用户的自定义模块 const custom = require('./custom.js') // 3. 加载第三方模块 const moment = require('moment')注意:使用
require()方法加载其他模块时,会执行被加载模块中的代码。
3、 Node.js 中的模块作用域
-
什么是模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
-
模块作用域的好处
防止了全局变量污染的问题
4、 向外共享模块作用域中的成员
-
module对象
在每个 .js 自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息
-
module.exports对象
在自定义模块中,可以使用
module.exports对象,将模块内的成员共享出去,供外界使用。外界用 require()方法 导入自定义模块时,得到的就是
module.exports所指向的对象。 -
共享成员时的注意点
使用
require()方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。 -
exports 对象
由于
module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports对象。默认情况下,exports 和 module.exports 指向同一个对象。 最终共享的结果,还是以 module.exports 指向的对象为准。 -
exports 和 module.exports 的使用误区
时刻谨记,
require()导入模块时,得到的永远是module.exports指向的对象。
注意: 当module.exports指向新对象之后,以module.exports指向的对象为准,而不是exports指向的对象。
注意: 为了防止混乱,建议大家不要在同一个模块中同时使用 exports 和 module.exports
5、 Node.js中的模块化规范
Node.js 遵循了 CommonJS模块化规范,CommonJS 规定了模块的特性和各个模块之间如何相互依赖。
CommonJS规定:
- 每个模块内部,module变量代表当前模块。
- module 变量是一个对象,它的exports属性(即module.exports)是对外的接口。
- 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。
模块的加载机制
1、优先从缓存中加载
模块在第一次加载后会被缓存。这也意味着多次调用 require()不会导致模块的代码被多次执行。(如下图导入三次模块,只打印了一次'ok',说明代码只执行了一次)
注意: 不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
2、内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
例如,require('fs') 始终返回的内置的 fs 模块,即使在 node_modules目录下有名字相同的包也叫做 fs。
3、自定义模块的加载机制
使用 require() 加载自定义模块时,必须指定以 ./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
- 按照确切的文件名进行加载
- 补全 .js 扩展名进行加载
- 补全 .json 扩展名进行加载
- 补全 .node 扩展名进行加载
- 加载失败,终端报错
4、第三方模块的加载机制
如果传递给 require()的模块标识符不是一个内置模块,也没有以 './' 或 '../' 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:\Users\leilei\project\foo.js' 文件里调用了 require('tools'),则Node.js 会按以下顺序查找:
- C:\User\leilei\project\node_modules\tools
- C:\User\leilei\node_modules\tools
- C:\User\node_modules\tools
- C:\node_modules\tools
5、目录作为模块
当把目录(没有指定确切文件,只是一个文件夹)作为模块标识符,传递给 require()进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做
package.json的文件,并寻找main属性,作为require()加载的入口 - 如果目录里没有
package.json文件,或者main入口不存在或无法解析,则 Node.js 将会试图加载目录下的index.js文件。 - 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'