一、模块化的定义
① 具有文件作用域
② 具有通信规则:加载和导出的规则
二、CommonJS模块规范
1. nodejs中的模块系统,具有文件作用域,也具有通信规则,使用require方法加载模块,使用exports接口对象导出模块的成员。
2. 加载require
① 语法:
var 自定义变量 = require("模块")
② 两个作用:执行被加载模块中的代码,得到加载模块中的exports导出接口对象。
3. 导出exports
① nodejs中的是模块作用域,默认文件中的所有成员只在当前文件模块有效
② 对于希望可以被其他模块访问的成员,可以挂载到exports接口的对象中
③ 导出多个成员(必须在对象中)
example.js:
// example.js
// 导出数字
exports.num = 123;
// 导出字符串
exports.str = 'nodejs'
// 导出函数
exports.fn1 = function() {
console.log('这是fn1')
}
// 导出对象
exports.obj1 = {
name: 'Jack',
age: '18'
}
index.js:
var example = require('./example')
console.log(example.num)
console.log(example.str)
console.log(example.fn1)
console.log(example.obj)
运行结果:
> demo node index.js
123
nodejs
[Function (anonymous)]
{ name: 'Jack', age: '18' }
④ 导出单个成员(拿到的就是函数、字符串等等,而不是一个对象)
example.js:
module.exports = '导出字符串'
index.js:
var example = require('./example')
console.log(example.str)
运行结果
> demo node index.js
nodejs
⑤ 以下情况会覆盖
example.js:
// example.js
module.exports='nodejs';
module.exports={
num:123,
str:'world'
}
index.js:
var example = require('./example')
console.log(example)
运行结果:
> demo node index.js
{ num: 123, str: 'world'}
⑥ 也可以这样来导出多个成员
example.js:
module.exports={
num:123,
str:'world',
add:function(x,y){
return x+y;
}
}
index.js:
var example = require('./example')
console.log(example)
运行结果:
> demo node index.js
{ num: 123, str: 'world', add: [Function: add] }
⑦ 原理解释:exports是module.exports的一个引用。
exports和module.exports的区别:
每个模块都有一个module对象,module对象中有一个exports对象,可以把需要导出的成员都挂载到module.exports接口对象中,也就是"module.exports.xxx = xxx"的方式。
但是每次这样书写太麻烦,所以nodejs为了更加方便,同时在每一个模块提供了一个成员叫“exports”,令“exports === module.exports’”结果为“true” 所以对于“module.exports”的方式完全可以使用“exports.xxx = xxx”。
当一个模块需要导出单个成员时,这个时候必须使用“module.exports”,使用 “exports = xxx”不管用。
因为每个模块最终向外“return”的是“module.exports”,而exports只是mudule.exports的一个引用,所以即便为“exports = xxx”重新赋值,也不会影响“module.exports”,并且如果重新赋值以后,exports和module.exports之间的引用关系会断裂。但是有一种赋值方式比较特殊,“exports = mudule.exports”这个是用来重新建立引用关系的。
a.js:
exports.foo = 'bar'
// 返回 { foo: 'bar' }
module.exports.a = 123
// 返回 { foo: 'bar', a: 123 }
exports = { a: 456 }
// 返回 { foo: 'bar', a: 123 } 无变化
module.exports.foo = '这是foo'
// 返回 { foo: '这是foo', a: 123 }
exports.c = 666
// 返回 { foo: '这是foo', a: 123 } 无变化
exports = module.exports;
// 重新建立了引用关系
exports.a = 789
// 返回 { foo: '这是foo', a: 789 }
index.js:
var a = require('./a')
console.log(a)
4. require方法
的加载规则
① 优先从缓存加载:可以避免重复加载,提高加载效率
执行结果:
执行流程:
② 判断模块标识:require(“模块标识”)
- 如果是路径形式的模块:
a. ./ 表示当前目录
‘ . ’ 和 ‘ / ’都不可以省略,可以省略文件的后缀名,如require(’./b’)
b. …/ 表示上一级目录
‘ … ’ 和 ‘ / ’ 都不可以省略,可以省略文件的后缀名,如require(’…/b’)
c. / ,表示当前文件模块所属磁盘根路径
如require(’/b’)
- 如果是核心模块:
本质也是文件,核心模块文件已经被编译到了二进制文件中,所以只需要按照文件名字加载即可,比如:require(‘fs’), require(‘http’)
- 如果是第三方模块:
① 第三方模块都必须通过npm来下载,来使用的时候可以通过require(“包名”)的方式来加载,不可能有任何一个第三方包和核心模块的名字是一样的
② 使用npm 下载包的时候,会在项目目录产生一个 node_modules 的文件夹(一个项目有且只有一个)
③ 第三方模块的加载规则解释 (参考文章:https://www.infoq.cn/article/nodejs-module-mechanism/)
三、Node.js 中的 4 类模块
原生模块和3种文件模块
原生模块 - 核心模块:
核心模块即是nodejs本身提供的模块,也叫原生模块。
· 文件操作的 fs
· http 服务的http
· url 路径操作模块
· path 路径操作模块
· os 操作系统信息
文件模块
- .js
- .json
- .node
Node.js 的 require 方法中的文件查找策略:
由于Node.js 中存在 4 类模块(原生模块和3种文件模块),尽管 require 方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同
从文件模块缓存中加载
尽管原生模块与文件模块的优先级不同,但是都会优先从文件模块的缓存中加载已经存在的模块。
从原生模块加载
原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个 http/http.js/http.node/http.json 文件,require(“http”) 都不会从这些文件中加载,而是从原生模块中加载。
原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行
从文件加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js 会解析 require 方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。
require方法接受以下几种参数的传递:
- http、fs、path等,原生模块。
- ./mod或…/mod,相对路径的文件模块。
- /pathtomodule/mod,绝对路径的文件模块。
- mod,非原生模块的文件模块。
在路径 Y 下执行 require(X) 语句执行顺序:
1. 如果 X 是内置模块
a. 返回内置模块
b. 停止执行
2. 如果 X 以 '/' 开头
a. 设置 Y 为文件根路径
3. 如果 X 以 './' 或 '/' or '../' 开头
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. 抛出异常 "not found"
LOAD_AS_FILE(X)
1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。
2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。
3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。
4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。
LOAD_INDEX(X)
1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。
2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。
3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。
LOAD_AS_DIRECTORY(X)
1. 如果 X/package.json 是一个文件,
a. 解析 X/package.json, 并查找 "main" 字段。
b. let M = X + (json main 字段)
c. LOAD_AS_FILE(M)
d. LOAD_INDEX(M)
2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIRS + DIR
d. let I = I - 1
5. return DIRS