模块系统(module)
模块化编程,将大的功能拆分成小的模块组成
什么是模块化
问题
当你的网站开发越来越复杂代码越来越多的时候会经常遇到什么问题?
- 烦人的命名冲突
- 繁琐的文件依赖
模块化优点
- 生产效率高
- 可维护性好(功能模块易于更改)
模块间通信
需要在其他模块间使用,调用某个模块的方法,提供方法共别的模块使用
通信规则
关键词:
- import (es标准的写法)
- require
- exports
require
模块导入
const fs = require('fs');
exports
模块导出
导出多个成员
- 写法一:(麻烦 不推荐)
module.exports.a = 123;
module.exports.b = 222;
module.exports.c = 888;
-
写法二:(推荐)
node 为了减低开发人员的痛苦,所以为
module.exports
提供了一个别名exports
console.log(exports === module.exports) // => true
exports.a = 123;
exports.b = 222;
exports.c = 789;
exports.fn = function () {
console.log('hello');
}
-
写法三
代码少可以,代码多了就不推荐了
module.exports = {
a: 123,
b: 222,
c: 888,
fn: function() {
console.log('hello');
}
}
导出单个成员
- 导出的错误写法与正确写法对比
// 导出单个成员,错误写法 因为每个模块最终导出的是 module.exports 而非 exports 所以
// 下面这种写法错误
// exports = function (x, y) {
// return x + y;
// }
// 正确写法
module.exports = function (x, y){
return x + y;
}
为什么 exports = xxx
不行
exports
是module.exports
的一个引用
// fn可以想象成一个模块
function fn() {
// 每个模块内部有一个 module 对象
// module 对象中有一个成员 exports 也是一个对象
var module = {
exports: {}
}
// 模块中同时还有一个成员 exports 等价于 module.exports
var exports = module.exports
console.log(exports === module.exports) // => true
// 这样是可以的,因为 exports === module.exports
// module.exports.a = 123
// exports.b = 456
// 这里重新赋值不管用,因为模块最后 return 的是 module.exports
// exports = function () {
// }
// 这才是正确的方式
module.exports = function () {
console.log(123)
}
// 最后导出的是 module.exports
return module.exports
}
var ret = fn()
console.log(ret)
exports
和module.exports
的区别
- 每个模块中都有一个
module
对象 -
module
对象中有一个exports
对象 - 我们可以把需要导出的成员都挂载到
module.exports
接口对象中也就是:moudle.exports.xxx = xxx
的方式,但是每次都moudle.exports.xxx = xxx
很麻烦,点儿的太多了;所以Node
为了你方便,同时在每一个模块中都提供了一个成员叫:exports
这个exports === module.exports
结果为true
- 多个成员导出:
moudle.exports.xxx = xxx
的方式 完全可以 - 单个成员导出:
expots.xxx = xxx
当一个模块需要导出单个成员的时候,这个时候必须使用:module.exports = xxx
的方式 不要使用exports = xxx
不管用因为每个模块最终向外return
的是module.exports
- 而
exports
只是module.exports
的一个引用所以即便你为exports = xx
重新赋值,也不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的
特殊的导出方式
// 重新建立引用关系
exports = module.exports = function () {
console.log('默认函数被调用了')
}
exports.ajax = function () {
console.log('ajax 方法被调用了')
}
exports.get = function () {
console.log('get 方法被调用了')
}
模块分类
在开始了解具体的规则之前,我们先来了解一下在 Node 中对不模块的一个具体分类,一共就三种类别;
node中的模块类别
- 核心模块
- 由 Node 本身提供,著名的,例如
fs
文件操作模块、http
网络操作模块
- 由 Node 本身提供,著名的,例如
- 第三方模块
- 由第三方提供,使用的时候我们需要通过
npm
进行下载然后才可以加载使用,例如我们使用过的mime
、art-template
、marked
- 注意:不能有第三方包的名字和核心模块的名字是一样的,否则会
造成冲突
- 由第三方提供,使用的时候我们需要通过
- 用户模块
- 我们在文件中写的代码很多的情况下不好编写和维护,所以我们可以考虑把文件中的代码拆分到多个文件中,那这些我们自己创建的文件就是
用户模块
- 我们在文件中写的代码很多的情况下不好编写和维护,所以我们可以考虑把文件中的代码拆分到多个文件中,那这些我们自己创建的文件就是
核心模块
- 核心模块就是 node 内置的模块,需要通过唯一的标识名称来进行获取。
- 每一个核心模块基本上都是暴露了一个对象,里面包含一些方法供我们使用
- 一般在加载核心模块的时候,变量的起名最好就和核心模块的标识名同名即可
- 例如:
const fs = require('fs')
- 例如:
- 核心模块本质上也是文件模块
核心模块已经被编译到了node
的可执行程序,一般看不到
可以通过查看node
的源码看到核心模块文件
核心模块也是基于CommonJS
模块规范
Node 中都以具名的方式提供了不同功能的模块,例如操作文件就是:fs
核心模块(系统模块)由 Node
提供,使用的时候都必须根据特定的核心模块名称来加载使用。例如使用文件操作模块: fs
例如:
const fs = require('fs');
常见核心模块
模块名称 | 作用 |
---|---|
fs | 文件操作 |
http | 网络操作 |
path | 路径操作 |
url | url地址操作 |
os | 操作系统信息 |
net | 更加底层的网络操作方式 |
querystring | 解析查询字符串模块 |
util | 工具函数模块 |
... | ... |
用户自定义模块(基于文件的模块)
以 ./ 或 ../ 开头的模块标识就是文件模块,一般就是用户编写的。
第三方模块
- moment
- marked
- ...
一般就是通过 npm install 安装的模块就是第三方模块。
加载规则如下:
- 如果不是文件模块,也不是核心模块
node
会去node_modules
目录中找(找跟你引用的名称一样的目录),例如这里require('underscore')
- 如果在
node_modules
目录中找到underscore
目录,则找该目录下的package.json
文件;如果找到package.json
文件,则找该文件中的main
属性,拿到main
指定的入口模块 - 如果过程都找不到,node 则取上一级目录下找 node_modules 目录,规则同上。。。
- 如果一直找到代码文件的根路径还找不到,则报错。。。
深入模块加载机制
模块记载流程图
nodejs-require.851bca4a.jpg2015-07-15_55a6794639322.1416ddab.jpg
模块加载概述
-
简而言之,如果
require
绝对路径的文件,查找时不会去遍历每一个node_modules
目录,其速度最快。其余流程如下: -
从
module path
数组中取出第一个目录作为查找基准。直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。 -
尝试添加
.js
、.json
、.node
后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。 -
尝试将
require
的参数作为一个包来进行查找,读取目录下的package.json
文件,取得main
参数指定的文件。 -
尝试查找该文件,如果存在,则结束查找。如果不存在,则进行
第3条
查找。 -
如果继续失败,则取出
module path
数组中的下一个目录作为基准查找,循环第1至5个步骤
。 -
如果继续失败,循环
第1至6个步骤
,直到module path
中的最后一个值。 -
如果仍然失败,则抛出异常。
整个查找过程十分类似原型链的查找和作用域的查找。所幸Node.js
对路径查找实现了缓存机制,否则由于每次判断路径都是同步阻塞式进行,会导致严重的性能消耗。
网友评论