Node.js的模块系统

一、模块化的定义

① 具有文件作用域
② 具有通信规则:加载和导出的规则

二、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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值