一、说在前面
在Node.js中,以模块为单位划分功能,通过一个完整的模块加载机制使得开发人员可以将应用程序划分为多个不同的部分。模块的使用可以提高代码重用率,提高应用程序的开发效率,而且开发人员可以根据具体的需求引入第三方模块或者自定义模块到应用程序中。
二、模块引入与常用内置对象
1. module
nodejs认为一个js文件就是一个模块,每个模块都有一个全局对象
module
,同时module
对象中有一个对象exports
,这个对象被加载一次之后会被缓存,里面提供了模块的父子模块关联信息,即父模块被哪些模块引用,子模块引用了哪些模块。
Module {
id: '.',
exports: {},
parent: null,
filename: 'E:\\workspace\\myRequre.js',
loaded: false,
children: [],
paths: [ 'E:\\workspace\\ node_modules', 'E:\\workspace\\node_modules', 'E:\\node_modules' ]
}
2. exports
exports
是module.exports
对象的别名,提供便捷的属性和方法设置。
3. require
require
可以加载文件模块(.js、.json、.node
)和nodejs
核心模块,最终获取到的是module.exports
对象。第一次加载的时候根据路径或模块名称进行查找,第二次从缓存中获取到module.exports
对象,如果没有发现指定模块就会抛出异常。
手动模拟require
方法
在下述代码中,
source
变量即为path
路径对应文件的二进制内容。该内容与字符串进行拼接后成为string
类型。该文件中的内容有同名exports
变量,又因为package
的字符串对应的匿名函数的参数也有exports
变量,故相当于为参数exports
添加属性,故当return module.exports
时,将参数module
中的exports
对象返回,即改变后的exports
对象。
function MyRequre(path){
function Module(){
this.exports = {};
}
var fs = require('fs');
var source = fs.readFileSync(path,'utf8');
var package = "(function(exports,module){"+source+" return module.exports;})";
var callback = eval(package);//将字符串转成函数
var module = new Module();
var fn = callback(module.exports, module);
return fn;
}
//foo.js
function a(){
console.log("1111");
}
exports.a =a;
//使用
var fn = MyRequre('./foo.js');
console.log(fn.a());//1111
三、模块加载机制
1. package
包是将一堆的文件联系起来的一种机制,
nodejs
就是在模块的基础之上进一步组织js
代码。
规范的包目录结构
文件/目录 | 说明 |
---|---|
package.json | 包描述文件。不仅开发者阅读使用,nodejs 也使用(查找文件时…) |
bin | 存放可执行的文件目录 |
lib | 存放js 的目录 |
doc | 存放文档的目录 |
test | 单元测试用例代码 |
package.json
文件
1. 文件大致模板
{
"name": "test",
"version": "0.0.1",
"description": "",
"main": "index.js",
"keywords": [
],
"author": "KingNigel",
"repository": {
"type": "git",
"url": "https://github.com/KingNigel/test.git"
},
"bugs": {
"url": "https://github.com/KingNigel/test/issues"
},
"license": "MIT",
"devDependencies": {
"express": "4.*"
},
"dependencies": {
"express": "4.*"
}
}
2. 文件属性描述
属性 | 描述 |
---|---|
name | 包名 |
description | 包描述 |
version | 包版本号 |
keywords | 关键词组,在npm 中分类搜索使用 |
author | 包作者 |
main | 配置包入口,默认是包的根目录下的index.js |
dependencies | 包依赖项,npm会自动加载依赖包 |
scripts | 指定运行脚本命令npm 命令行 |
2. npm
① npm install
:本地安装
- 将安装包下载到
./node_modules
下(运行npm命令时所在的目录),如果没有node_modules
目录,会在当前执行npm
的命令的目录下生成node_modules
目录- eg:如果当前目录是
c:\123>npm install ***
,则该模块会被安装到c:\123\node_modules\***
- 可以通过
require()
来引入安装的包- 该模块只能在本工程下使用
②npm install -g
:全局安装
- 模块将被下载安装到全局目录中,该模块可在全局使用
- 可通过
npm config set prefix "目录路径"
来设置。- eg: 当使用了
npm install -g express
安装了express
框架后, 我们就可以在电脑里的某一个文件夹下,打开控制台,直接使用express mvc
创建项目,否则会遇到"express’不是内部或外部命令,也不是可运行的程序”错误
四、node模块
node模块分为核心模块和文件模块
模块文件
- 后缀名为
.js
的javascript
脚本文件 - 后缀名为
.json
的json
文本文件 - 后缀名为
.node
的经过编译的二进制模块文件。
1. 核心模块
- 核心模块的源码都在lib子目录中,为了提高运行速度,他们安装的时候,都会被编译成二进制文件(后缀名为.node)。
- 在引入核心模式时直接使用
require("模块名称")
即可,不需要写路径。- 在引入核心模块时,如果写错模块名则
require
方法会抛出异常。- 核心模块具有最高的加载优先权,如果有模块与其名称冲突,Node.js总是加载核心模块
2. 文件模块
- 文件模块如果不加上扩展名,则在查找时会按照
.js、.json、.node
的顺序为其加上扩展名- 文件模块有两种加载形式:① 按路径加载 ② 在
node_modules
文件中查找加载- 文件模块按路径加载又分为 ① 按照绝对路径加载 ② 按照相对路径加载
模块加载的顺序
在加载模块时先去缓存中查找,如果为查找到再进行以下情况判断。
1. 模块有路径但没有扩展名:
按照其path
先定位到对应的路径下,根据.js、.json、.node
的顺序为path
中的文件名加上后缀进行文件查找,如果找到则返回,如果未找到,则会将path
中的文件名视为目录名进行查找,如果找到同名目录,则定位到该目录下,先去查找该目录下的package.json
文件,通过JSON.parse()
解析出包描述对象,再取出main
属性指定的文件名进行定位。如果文件缺少扩展名,将会进入扩展名分析的步骤。如果该目录下没有package.json
或者main
属性指定的文件名错误,则会将index
当做默认的文件名,然后依次查找index.js、index.json、index.node
,如果找到则返回,如果未找到则require
方法会抛出异常。
2. 模块有路径且有扩展名
按照其path
定位到对应的路径下,根据文件名查找对应的文件,如果找到则返回,如果未找到,require
方法会抛出异常。
3. 模块没有路径且没有扩展名
① 先去查找核心模块,如果不是核心模块则转到步骤②,否则返回核心模块。
② 去当前目录下的node_modules
按照.js、.json、.node
的顺序为模块名称加上扩展名然后去查找,如果找到则返回文件,否则将模块名称视为目录名称,在node_modules/目录名
下查找package.json
文件,通过JSON.parse()
解析出包描述对象,再取出main
属性指定的文件名进行定位。如果文件缺少扩展名,将会进入扩展名分析的步骤。如果该目录下没有package.json
或者main
属性指定的文件名错误,则会将index
当做默认的文件名,然后依次查找index.js、index.json、index.node
,如果找到则返回,如果未找到则执行步骤③
③ 去父目录中的node_modules
中继续按照步骤②查找,直到根目录。如果到根目录还未找到文件,则执行步骤④。
④在全局目录查找:如果在操作系统的环境变量中设置NODE_PATH
变量,并且已经将变量值设置为一个有效的磁盘目录,在使用require()
方法加载模块时只指定了模块的名称而没有指定模块的路径,而且Node.js从其他路径中找不到需要被加载的模块文件时,Node.js将会从NODE_PATH
变量值所指向的磁盘目录中寻找并加载模块文件。如果最终都没有找到,require()
方法就会抛出异常。
4. 模块没有路径且有扩展名
① 去当前目录的node_modules
下查找该文件名,如果有则返回,如果没有则去父目录中的node_modules
下查找,直至到根目录,如果仍旧未找到,则会去全局目录下查找,依旧未找到则require
方法会抛出异常。
require
方法的查找策略
参考文档:https://www.jianshu.com/p/f740287a5df7
https://blog.youkuaiyun.com/u013174239/article/details/79705542
https://blog.youkuaiyun.com/WuLex/article/details/82225210