正文
1、模块的基本介绍
(1)模块的介绍
在 Node 中,每个文件都可以看作是一个模块,每个模块都有自己的作用域,存在一个属于自己的命名空间
但是模块之间绝对不是孤立存在的,不同模块之间需要相互协作才能发挥作用,因此模块还要有对外暴露的接口
(2)模块的分类
在 Node 中,模块分为三类,分别是核心模块(也称内置模块)、第三方模块和自定义模块
- 核心模块 是 Node 的内置模块,被编译成二进制文件,放在 lib 文件夹下
- 第三方模块 一般通过 npm install 下载,放在 node_modules 文件夹下
- 自定义模块 由用户自己编写,可以放在项目中的任何位置
2、模块的导入
(1)加载的顺序
我们可以使用 require
语句导入模块,其接收一个字符串作为 模块标识,模块加载的顺序如下:
若模块标识是绝对路径或相对路径(以 /
、./
或 ../
开头),将会被识别为 自定义模块
- 如果没有省略文件后缀,例如
require('./entrance.js')
- 将会直接在指定目录下查找指定文件,如果没有找到,抛出异常
Cannot find module
- 如果省略文件后缀,例如
require('./entrance')
首先在指定目录下查找,顺序如下:entrance.js -> entrance.json -> entrance.node如果没有找到,将 entrance 当作一个目录,在该目录下继续查找
顺序如下:package.json(main 字段) -> index.js -> index.json -> index.node
如果没有找到,抛出异常 Cannot find module
如果模块标识不是绝对路径或相对路径,将会被识别为 核心模块 或 第三方模块
如果是核心模块,例如 require('http')
- 直接查找核心模块,如果没有找到,抛出异常
Cannot find module
若是第三方模块,例如 require('express')
- 首先在当前目录中的
node_modules
目录下查找 - 如果没有找到,那么在上一级目录中的
node_modules
目录下继续查找,直至文件系统的根目录 - 如果没有找到,抛出异常
Cannot find module
可以看到,加载模块的整个过程是十分复杂的,幸好 Node 为我们提供 缓存机制,可以提高加载速度
- 每个模块都会在第一次加载时被缓存,之后如果需要再次加载,将会直接读取缓存
- 每次加载模块首先从缓存中查找,如果在缓存中没有找到,才会按照上面的加载顺序执行
(2)加载的实质
使用 require
语句加载模块,实际上是同时完成两件事情
一是执行加载模块中的代码,二是返回加载模块中的 module.exports
,先看下面的例子:
// a.js module.exports = function(x , y) { return x + y } console.log('a') var subtract = require('./b.js') var result = subtract(5, 2) console.log(result) // 3
// b.js module.exports = function(x , y) { return x - y } console.log('b') var add = require('./a.js') var result = add(5, 2) console.log(result) // 7
输入 node a.js
命令,运行 a.js
文件,结果如下(注意一下打印顺序):
// a // b // 7 // 3
使用 require
语句,有两个地方需要注意:
- 当遇到
require
语句时,会先执行require
语句中指定的文件,而挂起当前文件的执行 - 无论模块被
require
多少次,只会执行一次
3、模块的导出
(1)module.exports
每个模块都有一个 module
对象,每个 module
对象都有一个 exports
属性,也是一个对象
当我们需要导出数据时,可以将数据挂载到 module.exports
下即可
- 导出多个成员
// 使用 require 得到的是 object // 方法一 module.exports.number = 123 module.exports.string = 'hello' module.exports.object = {} module.exports.array = [] // 方法二 module.exports = { number: 123, string: 'hello', object: {}, array: [] }
- 导出单个成员
module.exports = function (x, y) { // 使用 require 得到的是 function return x + y }
(2)exports
为了使用方便,Node 提供 exports
作为 module.exports
的引用
当我们需要导出多个成员时,可以使用以下的写法:
// 方法一 exports.number = 123 exports.string = 'hello' exports.object = {} exports.array = [] // 方法二 exports = { number: 123, string: 'hello', object: {}, array: [] }
但是不能使用 exports
导出单个成员,因为模块最终导出的是 module.exports
而当我们给 exports
重新赋值时,会使得 exports
指向 module.exports
的引用失效
4、模块的本质
对于一个模块而言,有两个关键的地方,一是有自己的作用域,二是有对外暴露的接口
不知道大家有没有想过这样一个问题,模块是怎么实现上面两个特性的呢?其实通过一个 立即执行函数 就可以了
实际上 Node 在编译的时候,会在我们写的代码外包上一层立即执行函数,并传入一些必须的参数
- exports:用于导出模块数据,module.exports 的一个引用
- require:用于导入其它模块
- module:包含当前模块的基本信息
- __filename:当前模块的绝对路径
- __dirname:当前模块所在目录的绝对路径
(function (exports, require, module, __filename, __dirname) { /* 我们写的代码 */ })()
我们上面使用的 require
、exports
都是在编译时传入的,我们可以把传入的参数打印出来看一下
// 打印 console.log(__filename) console.log(__dirname) console.log(exports) console.log(module) console.log(require) /* * 执行结果: * C:\Users\username\Desktop\index.js * C:\Users\username\Desktop * {} * Module { * id: '.', * exports: {}, * parent: null, * filename: 'C:\\Users\\username\\Desktop\\index.js', * loaded: false, * children: [], * paths: * [ 'C:\\Users\\username\\Desktop\\node_modules', * 'C:\\Users\\username\\node_modules', * 'C:\\Users\\node_modules', * 'C:\\node_modules' * ] * } * { [Function: require] * resolve: { [Function: resolve] paths: [Function: paths] }, * main: Module { * id: '.', * exports: {}, * parent: null, * filename: 'C:\\Users\\username\\Desktop\\index.js', * loaded: false, * children: [], * paths: * [ 'C:\\Users\\username\\Desktop\\node_modules', * 'C:\\Users\\username\\node_modules', * 'C:\\Users\\node_modules', * 'C:\\node_modules' * ] * }, * extensions: [Object: null prototype] { * '.js': [Function], * '.json': [Function], * '.node': [Function] * }, * cache: [Object: null prototype] { * 'C:\\Users\\username\\Desktop\\index.js': Module { * id: '.', * exports: {}, * parent: null, * filename: 'C:\\Users\\username\\Desktop\\index.js', * loaded: false, * children: [], * paths: [Array] * } * } * }
文章知识点与官方知识档案匹配,可进一步学习相关知识