做为还在漫漫前端学习路上的一位自学者。我以学习分享的方式来整理本身对于知识的理解,同时也但愿可以给你们做为一份参考。但愿可以和你们共同进步,若有任何纰漏的话,但愿你们多多指正。感谢万分!html
以编程角度来讲, "模块" 指的是可以提供必定功能或数据的程序语句集合. 模块具有和外部联系的接口 (其余模块或程序调用该模块的方式)前端
在 Node.js 中, 每一个文件就被视为一个模块. 这个文件多是 JavaScript 编写的文件、JSON 或者用 C/C++ 编译的二进制文件. 经过对外接口来向外部暴露功能或者数据, 模块之间能够互相调用.node
随着开发复杂度的提高, 将代码都写在一处的传统开发方式, 显现出了不少问题:npm
经过使用模块机制, 咱们能够把一个复杂程序的各个功能拆分, 分别封装到不一样的模块. 每一个模块职责单一 (各管一件事, 之间没交集) 经过开发新模块, 和对已有模块的复用来实现各类功能. 这种开发方式被称为 "模块化开发".编程
应用模块化开发, 使得各个功能都封装在独立的文件中, 分而治之, 互不干扰. 使得代码易于维护和复用. 同时每一个模块中的变量也不会污染全局做用域, 避免了命名冲突.json
Node.js 参照 CommonJS 标准实现了模块机制. CommonJS 是一套代码规范, 目的是为了构建 JavaScript 在浏览器以外的生态系统 (服务器端, 桌面端). JavaScript 诞生之初只是为了写网页小脚本, 并不做为开发大型复杂应用的语言, 其自身有不少不足. 而且, 官方规范 (ECMAScript) 制定的时间较早, 涵盖范围较小, 对于后端开发而言, 例如文件系统, I/O 流, 模块系统, 等等方面都没有相应的标准. 基于种种的不足, CommonJS 规范致力于弥补 JavaScript 没有标准的缺陷, 让 JavaScript 有能力去开发复杂应用, 同时具有跨平台能力.后端
下面是一个 Node.js 的模块使用示例:浏览器
在代码中, 开头经过 require
方法引入了 Node.js 自带的 http
模块. 并用此模块实现了一个 HTTP 服务器.服务器
const http = require('http');
function myNodeServer(req, res){
res.writeHead(200, {'Content-type':'text/plain'});
res.write('Hello World');
res.end();
}
http.createServer(myNodeServer).listen(3000); //监听 3000 端口
console.log('Server is running!');
复制代码
前文说, 在 Node.js 中, 每一个文件就被视为一个模块. 这个文件多是 JavaScript 编写的文件、JSON 或者用 C/C++ 编译的二进制文件.网络
模块能够分红三类:
http
, fs
, url
. 其分为 C/C++ 编写的和 JavaScript 编写的两部分. C/C++ 模块存放在 Node.js 源代码目录的 src/
目录下. JavaScript 模块存放在 lib/
目录下. NPM 是随同 Node.js 一块儿安装的 "包管理工具". 经过它, 全世界开发者们能够简单方便地互相分享和借鉴各自的 Node.js 模块. 其让整个 Node.js 社区生态变得繁荣热闹.
NPM 常见的使用场景有如下几种:
具体的使用方法网上有不少教程, 这里就不赘述了. 不想自行查阅的话, 能够直接参考下面的连接:
在了解了什么是模块以后, 让咱们来看看如何在 Node.js 中实际应用模块机制. 在使用上, 能够很简单的分为三个步骤: 建立, 导出, 引入. 先建立一个模块, 而后导出功能或数据, 模块之间能够互相引入导出的内容.
Node.js 提供了 exports
和 require
两个对象,其中 exports
用于导出模块, require
用于从外部引入另外一个模块, 即获取模块的 exports
对象.
先让咱们来看看如何建立并把模块的内容导出. 在 Node.js 中, 一个文件就是一个模块. 建立模块的方法就是建立一个文件.
经过 exports
对象来指定一个模块的导出内容.
示例:
// 文件名: nameModule.js
var name = 'Garrik';
exports.setName = function(newName) {
name = newName;
}
exports.getName = function() {
return name;
}
复制代码
在以上示例中, nameModule.js 文件经过 exports
对象将 setName
和 getName
做为模块的访问接口. 其余的模块能够引入导出的 exports
对象, 直接访问 exports
对象的成员函数.
在 Node.js 中, 经过 require
函数来引入外界模块导出的内容. require
函数接受一个字符串做为路径参数, 函数根据这个字符串参数来进行模块查找. 找到后会返回目标模块导出的 exports
对象.
示例:
// 文件名: showNameModule.js
var nameModule = require('./nameModule.js');
console.log(nameModule.getName());
// 显示: Garrik
nameModule.setName('Xiang');
console.log(nameModule.getName());
// 显示: Xiang
复制代码
上面示例中, 经过 require
引入了当前目录下 nameModule.js 导出的 exports
对象, 并让一个本地变量指向引入模块的 exports
对象. 以后在 showNameModule.js 文件中就可使用 getName
和 setName
这两个方法了.
在使用 exports
对象导出内容时, 全部做为对外访问接口的属性和方法都是定义在 exports
属性上的. 上面的例子中 setName
和 getName
方法都直接定义在 exports
对象上. 那若是想直接导出一个对象, 或者基础类型值可不能够呢?
可能有人会想可不能够这样写:
var name = 'Garrik';
exports = name;
复制代码
若是你试一下的话会发现, 最后引入的是一个空对象, 而不是你定义在 exports
上的东西.
在使用 exports
的时候只能往这个对象里添加新的属性和方法, 而不能对其直接赋值. 若是想直接导出一个对象, 或者基础类型值要使用 module.exports
对象. 例如上面例子就能够改写成:
// 文件名: nameModule.js
var name = 'Garrik';
module.exports = {
setName: function(newName) {
name = newName;
},
getName: function() {
return name;
}
}
复制代码
这样写的话, 就导出了一整个对象, setName
和 getName
方法是这个对象的成员函数. 而不是以前的 exports
对象了.
除此以外 module.exports
还能够直接导出基础类型值:
// 文件名: numMoule.js
var num = 123456;
module.exports = num;
复制代码
// 文件名: showNum.js
var getNum = require('./numModule.js'); // showNum.js 和 numModule.js 在同一目录下
console.log(getNum); // 结果: 123456
复制代码
这种方式下, 导出的就直接是基础类型的值.
可能仍是不少人在疑惑 exports
和 module.exports
区别和关系.
上面我说, 一个文件被另外一个模块引入时, 会被作一些处理. 文件中代码并不被 Node 执行, 而是被打包进一个函数中, 而后 Node 执行这个函数. 打包函数会被传入 exports
,require
,module
,__filename
,__dirname
这五个参数. 全部的这些参数都在 Node.js 执行函数时赋值, 而且只在当前的函数做用域中有效. 打包函数执行到最后, 返回 module.exports
对象.
其中, exports
是 module.exports
的引用, module
对象表明被打包进去的代码自己. module
的 exports
对象用于指定一个模块的导出内容.
在模块中定义外部可访问接口的时候, 有两个方法:
exports.name = 'Garrik';
复制代码
module.exports = {name: 'Garrik'};
复制代码
在使用 exports
的时候只能往这个对象里添加新的属性和方法, 而不能对其直接赋值. 由于直接赋值会打破其对 module.exports
的引用.
// 这是能够的:
exports.name = 'Garrik';
exports.gender = 'Male';
// 这是不能够的:
exports = {name: 'Garrik', gender: 'Male'};
// 应该用 module.exports:
module.exports = {name: 'Garrik', gender: 'Male'}
复制代码
若是想直接导出一个对象, 或基本类型值, 应该使用 module.exports
.
// 导出函数
module.exports = function(num) {
return num + 1;
};
// 导出基本类型值
module.exports = 123;
复制代码
在用 require
引入模块时, 路径参数可能有下面三种形式:
./
开头 或 ../
开头/
开头http
, fs
, url
)根据参数不一样, 加载方式也有区别.
在指定了模块路径的状况下, Node.js 会去指定的位置加载模块. 但由于用 require
来加载模块时能够省略文件后缀, 在省略的状况下, Node.js 会去猜想文件的类型.
比方说我要去 ./modules/
目录下加载一个 haha
模块.
var haha = require('./modules/haha');
复制代码
由于 haha
没写文件后缀, Node.js 将执行的操做顺序为:
haha
为一个目录, 则先查找该文件夹下的 package.json 文件,而后再加载该文件当中 main
字段所指定的入口文件. 若 package.json 文件当中没有 main
字段,或者根本没有 package.json 文件,则再默认查找该文件夹下的 index.js 文件, 并做为模块来载入.在没有路径, 参数值直接为一个模块名的状况下:
var haha = require('haha');
复制代码
haha
是 Node.js 核心模块就直接加载.haha
的所在. 如有两个同名文件,则遵循就近原则。优先引入目录顺序靠前的模块.haha
为一个目录, 则先查找该文件夹下的 package.json 文件,而后再加载该文件当中 main
字段所指定的入口文件. 若 package.json 文件当中没有 main
字段,或者根本没有 package.json 文件,则再默认查找该文件夹下的 index.js 文件, 并做为模块来载入.😆 好啦,今天的分享就告一段落啦。下一篇中,我会介绍如何实现一个 "Hello World" HTTP 服务器。
传送门: Node.js 系列 - 搭建 "Hello World" HTTP 服务器
若是喜欢的话就点个关注吧!O(∩_∩)O 谢谢各位的支持❗️