Node: 模块
我们知道,Node.js 选用 JavaScript 语言来编写代码。JavaScript 这门语言呢,之前主要用于前端应用,并没有相应的模块管理功能,而是以 script
标签为单位,直接引入即可运行。Node.js 主要运行在后端,这怎么办呢?好在它借鉴了 CommonJS
中的 Modules
规范,实现了一套易用的模块系统。今天,我们就来介绍一下 Node.js 中的模块。
举个栗子
下面我们写一段代码,封装一个模块,这个模块包含一个方法,对指定的人返回一句问候语:
// greeting.js
// 问候一声
exports.greet = (name) => {
return `hello ${name}`;
};
看上去挺简单的,直接在 exports
上面定义一个 greet
方法,即可将该方法暴露出去。
然后,我们在主文件中引入该模块,并调用它的 greet
方法:
// main.js
const greeting = require('./greeting');
console.log(greeting.greet('Scott'));
在引入该模块时,我们使用了相对路径 ./greeting
,Node.js 运行时会根据这个路径,找到我们自定义模块文件,然后编译执行。
最后,在命令行中运行 main.js
,程序会输出运行结果:
$ node main.js
hello Scott
一探究竟
下面,我们就来分析一下 Node.js 中的模块系统。
Node.js 中的模块分为 内置模块
和 自定义模块
两大类,而引用一个模块时一般会经历 路径分析
、文件定位
和 编译执行
三个步骤。
需要注意的是,如果我们引入内置模块,只需要 路径分析
这一个步骤,这是因为,内置模块在 Node.js 进程启动时,就已经被加载进内存了,可以直接引用,并且优先进行路径分析,所以不再需要 文件定位
和 编译执行
了。
引入内置模块时,只需指定模块名即可:
const http = require('http');
如果是引入自定义模块,则包含以下几种形式:
- 使用
.
或..
起始的相对路径 - 使用
/
起始的绝对路径 - 使用第三方模块名
如果使用了 相对路径
和 绝对路径
,require(module)
会根据模块路径读取相应的文件,然后编译执行,并将结果放入缓存,下次引入时直接从缓存中取结果。
如果我们引入一个第三方模块,例如 connect
模块,这时候,我们的引入方式和内置核心模块是类似的:
const connect = require('connect');
首先,Node.js 会先按内置模块查找,但发现它并不在内置模块名单中,所以接下来,要在 模块路径
中查找该模块对应的包。
这里提到了一个概念:模块路径
。它是 Node.js 定位文件模块的一种查找策略,表现形式是包含多个路径的一个数组。
为了验证这个数据结构,我们在代码中添加下面一行:
console.log(module.paths);
运行代码后,我们会得到下面输出内容:
[ '/Users/Scott/learning/node_modules',
'/Users/Scott/node_modules',
'/Users/node_modules',
'/node_modules' ]
可以看到,模块路径列举了这些目录:当前目录下的 node_modules 目录、父级目录下的 node_modules 目录、祖先目录下的 node_modules 目录、根目录下的 node_modules 目录。
在加载过程中,Node.js 会按照这个路径数组,逐个进行尝试,直到找到目标模块文件或包为止。
这里需要提醒一下,在使用 require(module)
时,如果参数不包含后缀名,Node.js 会按照 .js
、.node
、.json
次序补足后缀名,然后逐个尝试以单线程同步形式加载,所以,对于非 .js
后缀名的文件,引入时最好加上后缀名,以提高加载的速度。
回到我们最开始的一个小例子,这个程序中的 greeting.js
模块,通过 exports.greet
的形式导出一个方法,为什么能这样写呢,exports
是从哪里来的呢?
原来,Node.js 在加载一个模块时,会首先对它进行编译,在这个过程中,进行了一次头尾的包装,我们上面例子中的模块,经过包装之后是这个样子的:
(function (exports, require, module, __filename, __dirname) {
exports.greet = (name) => {
return `hello ${name}`;
};
});
前面我们直接拿来用的 exports
、require
以及 module
,原来是这么来的,还有 __filename
和 __dirname
,它们并不是全局变量,而是由包装函数传递进来的,表示当前模块文件的路径和目录。
经过上面的包装,我们的每个模块都有了自己的作用域,包装之后的代码会由 vm
原生模块的 runInThisContext()
方法执行,同时将包装函数所需的参数传递进去。
默认情况下,exports
和 module.exports
都指向一个空对象引用,即:
module.exports = exports = {};
所以上面我们给 exports
添加了一个方法,同时也会改变 module.exports
。
当然,我们也可以使用下面这种方式,改变导出的引用对象:
module.exports = {
greet: (name) => {
return `hello ${name}`;
}
};
但注意,不要在模块内使用下面这种方式:
// 无效的导出方式
exports = {
greet: (name) => {
return `hello ${name}`;
}
};
原因在于它更改的只是形参的引用,却对实参引用没有做任何更改,所以是无效的导出方式。
Node: 模块的更多相关文章
- node模块加载层级优化
模块加载痛点 大家也或多或少的了解node模块的加载机制,最为粗浅的表述就是依次从当前目录向上级查询node_modules目录,若发现依赖则加载.但是随着应用规模的加大,目录层级越来越深,若是在某个 ...
- node模块系统常用命令
node模块系统常用命令 命令 示例 备注 安装模块 npm install commander 最新版本 npm install commander@1.0.0 指定版本 npm install c ...
- Commonjs规范及Node模块实现
前面的话 Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于java ...
- 模块机制 之commonJs、node模块 、AMD、CMD
在其他高级语言中,都有模块中这个概念,比如java的类文件,PHP有include何require机制,JS一开始就没有模块这个概念,起初,js通过<script>标签引入代码的方式显得杂 ...
- NW.js安装原生node模块node-printer控制打印机
1.安装原生node模块 #全局安装nw-gyp npm install -g nw-gyp #设置目标NW.js版本 set npm_config_target=0.31.4 #设置构建架构,ia3 ...
- 深入了解Node模块原理
深入了解Node模块原理 当我们编写JavaScript代码时,我们可以申明全局变量: var s = 'global'; 在浏览器中,大量使用全局变量可不好.如果你在a.js中使用了全局变量s,那么 ...
- 【转】Commonjs规范及Node模块实现
前言: Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于javas ...
- [转]模块化——Common规范及Node模块实现
Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于javascrip ...
- node 模块正确暴露方法
一个node模块,为了能够服用,就需要将其暴露,那么如何正确写呢?(参考:https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Expr ...
- Electron结合React,在渲染进程中使用 node 模块
Electron结合React,在渲染进程中使用 node 模块 问题 将create-react-app与electron集成在了一个项目中.但是在React中无法使用electron.当在Reac ...
随机推荐
- server computer (实验室移动服务器环境)
star@xmatrix:~$ lshwWARNING: you should run this program as super-user.xmatrix ...
- cisco 路由与ASA SSH 设置
转载于https://www.cnblogs.com/sun292393989/p/8980700.html 一 试验拓扑 二 Server配置 ①配置hostname和domain name 因为r ...
- Java8 lambda表达式10个示例<转>
例1.用lambda表达式实现Runnable 我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例.看一下Java 8之前的runna ...
- Linux终端复制粘贴后前后会多出0~和~1
在终端中执行即可 printf "\e[?2004l" 在终端无法复制问题 set mouse=r
- EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器软件实现的多码率视频点播功能说明
关于EasyDSS EasyDSS(http://www.easydss.com)流媒体解决方案采用业界优秀的流媒体框架模式设计,服务运行轻量.高效.稳定.可靠.易维护,支持RTMP直播.RTMP推送 ...
- Zipkin+Sleuth 链路追踪整合
1.Zipkin 是一个开放源代码分布式的跟踪系统 它可以帮助收集服务的时间数据,以解决微服务架构中的延迟问题,包括数据的收集.存储.查找和展现 每个服务向zipkin报告计时数据,zipkin会根据 ...
- Apache显示目录列表及icons目录的问题
今天想部署下开源项目pig,发现它的mysql需要5.7.8 +,为了能支持多个版本并且可以方便切换,所以选择了phpstudy_pro 刚开始Apache不支持目录访问 修改配置 <Virtu ...
- 基础自动化部署搭建过程【Jenkins】
测试环境搭建 为了快速搭建一套PHP测试环境我决定用laradock了,虽然文件很多,但是里面封装的东西也是比较全的,后期开发不知道会用到什么技术,就决定先用这个,随时可以启动用得到的服务.larad ...
- C++ 智能指针 std::auto_ptr 分析
背景介绍: RAll机制 定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理,从而保证资源的正确初始化和清理 ps:智能指针就是RAll机制的一种应用,智 ...
- HLP帮助文件源文件RTF文件的编写
https://www.cnblogs.com/gaodu2003/archive/2008/12/17/1356861.html 举例说明如下: 每一节的标题在RTF文件中一般以特有的脚注($)指定 ...