Node.js 太火了,火到几乎全部前端工程师都想学,几乎全部后端工程师也想学。一说到 Node.js,咱们立刻就会想到“异步”、“事件驱动”、“非阻塞”、“性能优良”这几个特色,可是你真的理解这些词的含义吗?这篇教程将带你快速入门 Node.js,为后续的前端学习或是 Node.js 进阶打下坚实的基础。javascript
此教程属于Node.js 后端工程师学习路线的一部分,点击可查看所有内容。html
简单地说,Node(或者说 Node.js,二者是等价的)是 JavaScript 的一种运行环境。在此以前,咱们知道 JavaScript 都是在浏览器中执行的,用于给网页添加各类动态效果,那么能够说浏览器也是 JavaScript 的运行环境。那么这两个运行环境有哪些差别呢?请看下图:前端
两个运行环境共同包含了 ECMAScript,也就是剥离了全部运行环境的 JavaScript 语言标准自己。如今 ECMAScript 的发展速度很是惊人,几乎可以作到每一年发展一个版本。java
提示node
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。在平常场合,这两个词是能够互换的。更多背景知识可参考阮一峰的《JavaScript语言的历史》。webpack
另外一方面,浏览器端 JavaScript 还包括了:git
window
对象document
对象而 Node.js 则是包括 V8 引擎。V8 是 Chrome 浏览器中的 JavaScript 引擎,通过多年的发展和优化,性能和安全性都已经达到了至关的高度。而 Node.js 则进一步将 V8 引擎加工成能够在任何操做系统中运行 JavaScript 的平台。程序员
在正式开始这篇教程以前,咱们但愿你已经作好了如下准备:es6
这篇教程将会让你学到:github
运行 Node 代码一般有两种方式:1)在 REPL 中交互式输入和运行;2)将代码写入 JS 文件,并用 Node 执行。
提示
REPL 的全称是 Read Eval Print Loop(读取-执行-输出-循环),一般能够理解为交互式解释器,你能够输入任何表达式或语句,而后就会马上执行并返回结果。若是你用过 Python 的 REPL 必定会以为很熟悉。
若是你已经安装好了 Node,那么运行如下命令就能够输出 Node.js 的版本:
$ node -v
v12.10.0
复制代码
而后,咱们还能够进入 Node REPL(直接输入 node
),而后输入任何合法的 JavaScript 表达式或语句:
$ node
Welcome to Node.js v12.10.0.
Type ".help" for more information.
> 1 + 2
3
> var x = 10;
undefined
> x + 20
30
> console.log('Hello World');
Hello World
undefined
复制代码
有些行的开头是 >
,表明输入提示符,所以 >
后面的都是咱们要输入的命令,其余行则是表达式的返回值或标准输出(Standard Output,stdout)。运行的效果以下:
REPL 一般用来进行一些代码的试验。在搭建具体应用时,更多的仍是建立 Node 文件。咱们先建立一个最简单的 Node.js 脚本文件,叫作 timer.js,代码以下:
console.log('Hello World!');
复制代码
而后用 Node 解释器执行这个文件:
$ node timer.js
Hello World!
复制代码
看上去很是平淡无奇,可是这一行代码却凝聚了 Node.js 团队背后的心血。咱们来对比一下,在浏览器和 Node 环境中执行这行代码有什么区别:
console.log
调用了 BOM,实际上执行的是 window.console.log('Hello World!')
process.stdout.write('Hello World!\n')
简而言之,Node 为咱们提供了一个无需依赖浏览器、可以直接与操做系统进行交互的 JavaScript 代码运行环境!
若是你有过编写 JavaScript 的经验,那么你必定对全局对象不陌生。在浏览器中,咱们有 document
和 window
等全局对象;而 Node 只包含 ECMAScript 和 V8,不包含 BOM 和 DOM,所以 Node 中不存在 document
和 window
;取而代之,Node 专属的全局对象是 process
。在这一节中,咱们将初步探索一番 Node 全局对象。
在此以前,咱们先看一下 JavaScript 各个运行环境的全局对象的比较,以下图所示:
能够看到 JavaScript 全局对象能够分为四类:
window
、alert
等等;process
、Buffer
、__dirname
、__filename
等等;console
(第一节中已提到)、setTimeout
、setInterval
等;Date
、String
、Promise
等;process
process
全局对象能够说是 Node.js 的灵魂,它是管理当前 Node.js 进程状态的对象,提供了与操做系统的简单接口。
首先咱们探索一下 process
对象的重要属性。打开 Node REPL,而后咱们查看一下 process
对象的一些属性:
pid
:进程编号env
:系统环境变量argv
:命令行执行此脚本时的输入参数platform
:当前操做系统的平台提示
能够在 Node REPL 中尝试一下这些对象。像上面说的那样进入 REPL(你的输出颇有可能跟个人不同):
$ node Welcome to Node.js v12.10.0. Type ".help" for more information. > process.pid 3 > process.platform 'darwin' 复制代码
Buffer
Buffer
全局对象让 JavaScript 也可以轻松地处理二进制数据流,结合 Node 的流接口(Stream),可以实现高效的二进制文件处理。这篇教程不会涉及 Buffer
。
__filename
和 __dirname
分别表明当前所运行 Node 脚本的文件路径和所在目录路径。
警告
__filename
和__dirname
只能在 Node 脚本文件中使用,在 REPL 中是没有定义的。
接下来咱们将在刚才写的脚本文件中使用 Node 全局对象,分别涵盖上面的三类:
process
console
和 setTimeout
Date
提示
setTimeout
用于在必定时间后执行特定的逻辑,第一个参数为时间到了以后要执行的函数(回调函数),第二个参数是等待时间。例如:setTimeout(someFunction, 1000); 复制代码
就会在
1000
毫秒后执行someFunction
函数。
代码以下:
setTimeout(() => {
console.log('Hello World!');
}, 3000);
console.log('当前进程 ID', process.pid);
console.log('当前脚本路径', __filename);
const time = new Date();
console.log('当前时间', time.toLocaleString());
复制代码
运行以上脚本,在我机器上的输出以下(Hello World! 会延迟三秒输出):
$ node timer.js
当前进程 ID 7310
当前脚本路径 /Users/mRc/Tutorials/nodejs-quickstart/timer.js
当前时间 12/4/2019, 9:49:28 AM
Hello World!
复制代码
从上面的代码中也能够一瞥 Node.js 异步的魅力:在 setTimeout
等待的 3 秒内,程序并没有阻塞,而是继续向下执行,这就是 Node.js 的异步非阻塞!
提示
在实际的应用环境中,每每有不少 I/O 操做(例如网络请求、数据库查询等等)须要耗费至关多的时间,而 Node.js 可以在等待的同时继续处理新的请求,大大提升了系统的吞吐率。
在后续教程中,咱们会出一篇深刻讲解 Node.js 异步编程的教程,敬请期待!
Node.js 相比以前的浏览器 JavaScript 的另外一个重点改变就是:模块机制的引入。这一节内容很长,但倒是入门 Node.js 最为关键的一步,加油吧💪!
Eric Raymond 在《UNIX编程艺术》中定义了模块性(Modularity)的规则:
开发人员应使用经过定义明确的接口链接的简单零件来构建程序,所以问题是局部的,能够在未来的版本中替换程序的某些部分以支持新功能。 该规则旨在节省调试复杂、冗长且不可读的复杂代码的时间。
“分而治之”的思想在计算机的世界很是广泛,可是在 ES2015 标准出现之前(不了解不要紧,后面会讲到), JavaScript 语言定义自己并无模块化的机制,构建复杂应用也没有统一的接口标准。人们一般使用一系列的 <script>
标签来导入相应的模块(依赖):
<head>
<script src="fileA.js"></script>
<script src="fileB.js"></script>
</head>
复制代码
这种组织 JS 代码的方式有不少问题,其中最显著的包括:
<script>
没法被轻易去除或修改人们渐渐认识到了 JavaScript 模块化机制的缺失带来的问题,因而两大模块化规范被提出:
提示
ECMAScript 2015(也就是你们常说的 ES6)标准为 JavaScript 语言引入了全新的模块机制(称为 ES 模块,全称 ECMAScript Modules),并提供了
import
和export
关键词,若是感兴趣可参考这篇文章。可是截止目前,Node.js 对 ES 模块的支持还处于试验阶段,所以这篇文章不会讲解、也不提倡使用。
在正式分析 Node 模块机制以前,咱们须要明肯定义什么是 Node 模块。一般来讲,Node 模块可分为两大类:
其中,文件模块能够是一个单独的文件(以 .js
、.node
或 .json
结尾),或者是一个目录。当这个模块是一个目录时,模块名就是目录名,有两种状况:
main
字段指向的文件;.js
、.node
或 .json
,此文件则为模块入口文件。一会儿消化不了不要紧,能够先阅读后面的内容,忘记了模块的定义能够再回过来看看哦。
知道了 Node 模块的具体定义后,咱们来了解一下 Node 具体是怎样实现模块机制的。具体而言,Node 引入了三个新的全局对象(仍是 Node 专属哦):1)require
;2) exports
和 3)module
。下面咱们逐一讲解。
require
require
用于导入其余 Node 模块,其参数接受一个字符串表明模块的名称或路径,一般被称为模块标识符。具体有如下三种形式:
os
、express
等./utils
/home/xxx/MyProject/utils
提示
在经过路径导入模块时,一般省略文件名中的
.js
后缀。
代码示例以下:
// 导入内置库或第三方模块
const os = require('os');
const express = require('express');
// 经过相对路径导入其余模块
const utils = require('./utils');
// 经过绝对路径导入其余模块
const utils = require('/home/xxx/MyProject/utils');
复制代码
你也许会好奇,经过名称导入 Node 模块的时候(例如 express
),是从哪里找到这个模块的?实际上每一个模块都有个路径搜索列表 module.paths
,在后面讲解 module
对象的时候就会一清二楚了。
exports
咱们已经学会了用 require
导入其余模块中的内容,那么怎么写一个 Node 模块,并导出其中内容呢?答案就是用 exports
对象。
例如咱们写一个 Node 模块 myModule.js:
// myModule.js
function add(a, b) {
return a + b;
}
// 导出函数 add
exports.add = add;
复制代码
经过将 add
函数添加到 exports
对象中,外面的模块就能够经过如下代码使用这个函数。在 myModule.js 旁边建立一个 main.js,代码以下:
// main.js
const myModule = require('./myModule');
// 调用 myModule.js 中的 add 函数
myModule.add(1, 2);
复制代码
提示
若是你熟悉 ECMAScript 6 中的解构赋值,那么能够用更优雅的方式获取
add
函数:const { add } = require('./myModule'); 复制代码
module
经过 require
和 exports
,咱们已经知道了如何导入、导出 Node 模块中的内容,可是你可能仍是以为 Node 模块机制有一丝丝神秘的感受。接下来,咱们将掀开这神秘的面纱,了解一下背后的主角——module
模块对象。
咱们能够在刚才的 myModule.js 文件的最后加上这一行代码:
console.log('module myModule:', module);
复制代码
在 main.js 最后加上:
console.log('module main:', module);
复制代码
运行后会打印出来这样的内容(左边是 myModule,右边是 module):
能够看到 module
对象有如下字段:
id
:模块的惟一标识符,若是是被运行的主程序(例如 main.js)则为 .
,若是是被导入的模块(例如 myModule.js)则等同于此文件名(即下面的 filename
字段)path
和 filename
:模块所在路径和文件名,没啥好说的exports
:模块所导出的内容,实际上以前的 exports
对象是指向 module.exports
的引用。例如对于 myModule.js,刚才咱们导出了 add
函数,所以出如今了这个 exports
字段里面;而 main.js 没有导出任何内容,所以 exports
字段为空parent
和 children
:用于记录模块之间的导入关系,例如 main.js 中 require
了 myModule.js,那么 main 就是 myModule 的 parent
,myModule 就是 main 的 children
loaded
:模块是否被加载,从上图中能够看出只有 children
中列出的模块才会被加载paths
:这个就是 Node 搜索文件模块的路径列表,Node 会从第一个路径到最后一个路径依次搜索指定的 Node 模块,找到了则导入,找不到就会报错提示
若是你仔细观察,会发现 Node 文件模块查找路径(
module.paths
)的方式实际上是这样的:先找当前目录下的 node_modules,没有的话再找上一级目录的 node_modules,还没找到的话就一直向上找,直到根目录下的 node_modules。
module.exports
以前咱们提到,exports
对象本质上是 module.exports
的引用。也就是说,下面两行代码是等价的:
// 导出 add 函数
exports.add = add;
// 和上面一行代码是同样的
module.exports.add = add;
复制代码
实际上还有第二种导出方式,直接把 add
函数赋给 module.exports
对象:
module.exports = add;
复制代码
这样写和第一种导出方式有什么区别呢?第一种方式,在 exports
对象上添加一个属性名为 add
,该属性的值为 add
函数;第二种方式,直接令 exports
对象为 add
函数。可能有点绕,可是请必定要理解这二者的重大区别!
在 require
时,二者的区别就很明显了:
// 第一种导出方式,须要访问 add 属性获取到 add 函数
const myModule = require('myModule');
myModule.add(1, 2);
// 第二种导出方式,能够直接使用 add 函数
const add = require('myModule');
add(1, 2);
复制代码
警告
直接写
exports = add;
没法导出add
函数,由于exports
本质上是指向module
的exports
属性的引用,直接对exports
赋值只会改变exports
,对module.exports
没有影响。若是你以为难以理解,那咱们用apple
和price
类比module
和exports
:apple = { price: 1 }; // 想象 apple 就是 module price = apple.price; // 想象 price 就是 exports apple.price = 3; // 改变了 apple.price price = 3; // 只改变了 price,没有改变 apple.price 复制代码
咱们只能经过
apple.price = 1
设置price
属性,而直接对price
赋值并不能修改apple.price
。
在聊了这么多关于 Node 模块机制的内容后,是时候回到咱们以前的定时器脚本 timer.js 了。咱们首先建立一个新的 Node 模块 info.js,用于打印系统信息,代码以下:
const os = require('os');
function printProgramInfo() {
console.log('当前用户', os.userInfo().username);
console.log('当前进程 ID', process.pid);
console.log('当前脚本路径', __filename);
}
module.exports = printProgramInfo;
复制代码
这里咱们导入了 Node 内置模块 os
,并经过 os.userInfo()
查询到了系统用户名,接着经过 module.exports
导出了 printProgramInfo
函数。
而后建立第二个 Node 模块 datetime.js,用于返回当前的时间,代码以下:
function getCurrentTime() {
const time = new Date();
return time.toLocaleString();
}
exports.getCurrentTime = getCurrentTime;
复制代码
上面的模块中,咱们选择了经过 exports
导出 getCurrentTime
函数。
最后,咱们在 timer.js 中经过 require
导入刚才两个模块,并分别调用模块中的函数 printProgramInfo
和 getCurrentTime
,代码以下:
const printProgramInfo = require('./info');
const datetime = require('./datetime');
setTimeout(() => {
console.log('Hello World!');
}, 3000);
printProgramInfo();
console.log('当前时间', datetime.getCurrentTime());
复制代码
再运行一下 timer.js,输出内容应该与以前彻底一致。
读到这里,我想先恭喜你渡过了 Node.js 入门最难的一关!若是你已经真正地理解了 Node 模块机制,那么我相信接下来的学习会无比轻松哦。
Node.js 做为能够在操做系统中直接运行 JavaScript 代码的平台,为前端开发者开启了无限可能,其中就包括一系列用于实现前端自动化工做流的命令行工具,例如 Grunt、Gulp 还有大名鼎鼎的 Webpack。
从这一步开始,咱们将把 timer.js 改形成一个命令行应用。具体地,咱们但愿 timer.js 能够经过命令行参数指定等待的时间(time
选项)和最终输出的信息(message
选项):
$ node timer.js --time 5 --message "Hello Tuture"
复制代码
process.argv
读取命令行参数以前在讲全局对象 process
时提到一个 argv
属性,可以获取命令行参数的数组。建立一个 args.js 文件,代码以下:
console.log(process.argv);
复制代码
而后运行如下命令:
$ node args.js --time 5 --message "Hello Tuture"
复制代码
输出一个数组:
[
'/Users/mRc/.nvm/versions/node/v12.10.0/bin/node',
'/Users/mRc/Tutorials/nodejs-quickstart/args.js',
'--time',
'5',
'--message',
'Hello Tuture'
]
复制代码
能够看到,process.argv
数组的第 0 个元素是 node
的实际路径,第 1 个元素是 args.js 的路径,后面则是输入的全部参数。
根据刚才的分析,咱们能够很是简单粗暴地获取 process.argv
的第 3 个和第 5 个元素,分别能够获得 time
和 message
参数。因而修改 timer.js 的代码以下:
const printProgramInfo = require('./info');
const datetime = require('./datetime');
const waitTime = Number(process.argv[3]);
const message = process.argv[5];
setTimeout(() => {
console.log(message);
}, waitTime * 1000);
printProgramInfo();
console.log('当前时间', datetime.getCurrentTime());
复制代码
提醒一下,setTimeout
中时间的单位是毫秒,而咱们指定的时间参数单位是秒,所以要乘 1000。
运行 timer.js,加上刚才说的全部参数:
$ node timer.js --time 5 --message "Hello Tuture"
复制代码
等待 5 秒钟后,你就看到了 Hello Tuture 的提示文本!
不过很显然,目前这个版本有很大的问题:输入参数的格式是固定的,很不灵活,好比说调换 time
和 message
的输入顺序就会出错,也不能检查用户是否输入了指定的参数,格式是否正确等等。若是要亲自实现上面所说的功能,那可得花很大的力气,说不定还会有很多 Bug。有没有更好的方案呢?
从这一节开始,你将再也不是一我的写代码。你的背后将拥有百万名 JavaScript 开发者的支持,而这一切仅须要 npm 就能够实现。npm 包括:
咱们首先打开终端(命令行),检查一下 npm
命令是否可用:
$ npm -v
6.10.3
复制代码
而后在当前目录(也就是刚才编辑的 timer.js 所在的文件夹)运行如下命令,把当前项目初始化为 npm 项目:
$ npm init
复制代码
这时候 npm 会提一系列问题,你能够一路回车下去,也能够仔细回答,最终会建立一个 package.json 文件。package.json 文件是一个 npm 项目的核心,记录了这个项目全部的关键信息,内容以下:
{
"name": "timer",
"version": "1.0.0",
"description": "A cool timer",
"main": "timer.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mRcfps/nodejs-quickstart.git"
},
"author": "mRcfps",
"license": "ISC",
"bugs": {
"url": "https://github.com/mRcfps/nodejs-quickstart/issues"
},
"homepage": "https://github.com/mRcfps/nodejs-quickstart#readme"
}
复制代码
其中大部分字段的含义都很明确,例如 name
项目名称、 version
版本号、description
描述、author
做者等等。不过这个 scripts
字段你可能会比较困惑,咱们会在下一节中详细介绍。
接下来咱们将讲解 npm 最最最经常使用的命令—— install
。没错,绝不夸张地说,一个 JavaScript 程序员用的最多的 npm 命令就是 npm install
。
在安装咱们须要的 npm 包以前,咱们须要去探索一下有哪些包能够为咱们所用。一般,咱们能够在 npm 官方网站 上进行关键词搜索(记得用英文哦),好比说咱们搜 command line:
出来的第一个结果 commander 就很符合咱们的须要,点进去就是安装的说明和使用文档。咱们还想要一个“加载中”的动画效果,提升用户的使用体验,试着搜一下 loading 关键词:
第二个结果 ora 也符合咱们的须要。那咱们如今就安装这两个 npm 包:
$ npm install commander ora
复制代码
少量等待后,能够看到 package.json 多了一个很是重要的 dependencies
字段:
"dependencies": {
"commander": "^4.0.1",
"ora": "^4.0.3"
}
复制代码
这个字段中就记录了咱们这个项目的直接依赖。与直接依赖相对的就是间接依赖,例如 commander 和 ora 的依赖,咱们一般不用关心。全部的 npm 包(直接依赖和间接依赖)所有都存放在项目的 node_modules 目录中。
提示
node_modules 一般有不少的文件,所以不会加入到 Git 版本控制系统中,你从网上下载的 npm 项目通常也只会有 package.json,这时候只需运行
npm install
(后面不跟任何内容),就能够下载并安装全部依赖了。
整个 package.json 代码以下所示:
{
"name": "timer",
"version": "1.0.0",
"description": "A cool timer",
"main": "timer.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mRcfps/nodejs-quickstart.git"
},
"author": "mRcfps",
"license": "ISC",
"bugs": {
"url": "https://github.com/mRcfps/nodejs-quickstart/issues"
},
"homepage": "https://github.com/mRcfps/nodejs-quickstart#readme",
"dependencies": {
"commander": "^4.0.1",
"ora": "^4.0.3"
}
}
复制代码
在软件开发中,版本号是一个很是重要的概念,不一样版本的软件存在或大或小的差别。npm 采用了语义版本号(Semantic Versioning,简称 semver),具体规定以下:
提示
向下兼容的简单理解就是功能只增不减。
所以在 package.json 的 dependencies
字段中,能够经过如下方式指定版本:
1.0.0
,必定只会安装版本为 1.0.0
的依赖1.0
、1.0.x
或 ~1.0.0
,那么可能会安装例如 1.0.8
的依赖1
、1.x
或 ^1.0.0
( npm install
默认采用的形式),那么可能会安装例如 1.1.0
的依赖*
或 x
,那么直接安装最新版本(不推荐)你也许注意到了 npm 还建立了一个 package-lock.json,这个文件就是用来锁定所有直接依赖和间接依赖的精确版本号,或者说提供了关于 node_modules 目录的精确描述,从而确保在这个项目中开发的全部人都能有彻底一致的 npm 依赖。
咱们在大体读了一下 commander 和 ora 的文档以后,就能够开始用起来了,修改 timer.js 代码以下:
const program = require('commander');
const ora = require('ora');
const printProgramInfo = require('./info');
const datetime = require('./datetime');
program
.option('-t, --time <number>', '等待时间 (秒)', 3)
.option('-m, --message <string>', '要输出的信息', 'Hello World')
.parse(process.argv);
setTimeout(() => {
spinner.stop();
console.log(program.message);
}, program.time * 1000);
printProgramInfo();
console.log('当前时间', datetime.getCurrentTime());
const spinner = ora('正在加载中,请稍后 ...').start();
复制代码
此次,咱们再次运行 timer.js:
$ node timer.js --message "洪荒之力!" --time 5
复制代码
转起来了!
在本教程的最后一节中,咱们将简单地介绍一下 npm scripts,也就是 npm 脚本。以前在 package.json 中提到,有个字段叫 scripts
,这个字段就定义了所有的 npm scripts。咱们发如今用 npm init
时建立的 package.json 文件默认就添加了一个 test
脚本:
"test": "echo \"Error: no test specified\" && exit 1"
复制代码
那一串命令就是 test 脚本将要执行的内容,咱们能够经过 npm test
命令执行该脚本:
$ npm test
> timer@1.0.0 test /Users/mRc/Tutorials/nodejs-quickstart
> echo "Error: no test specified" && exit 1
Error: no test specified
npm ERR! Test failed. See above for more details.
复制代码
在初步体验了 npm scripts 以后,咱们有必要了解一下 npm scripts 分为两大类:
test
、start
、install
、publish
等等,直接经过 npm <scriptName>
运行,例如 npm test
,全部预约义的脚本可查看文档npm run <scriptName>
运行,例如 npm run custom
如今就让咱们开始为 timer 项目添加两个 npm scripts,分别是 start
和 lint
。第一个是预约义的,用于启动咱们的 timer.js;第二个是静态代码检查,用于在开发时检查咱们的代码。首先安装 ESLint npm 包:
$ npm install eslint --save-dev
$ # 或者
$ npm install eslint -D
复制代码
注意到咱们加了一个 -D
或 --save-dev
选项,表明 eslint
是一个开发依赖,在实际项目发布或部署时不须要用到。npm 会把全部开发依赖添加到 devDependencies
字段中。而后分别添加 start
和 lint
脚本,代码以下:
{
"name": "timer",
"version": "1.0.0",
"description": "A cool timer",
"main": "timer.js",
"scripts": {
"lint": "eslint **/*.js",
"start": "node timer.js -m '上手了' -t 3",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mRcfps/nodejs-quickstart.git"
},
"author": "mRcfps",
"license": "ISC",
"bugs": {
"url": "https://github.com/mRcfps/nodejs-quickstart/issues"
},
"homepage": "https://github.com/mRcfps/nodejs-quickstart#readme",
"dependencies": {
"commander": "^4.0.1",
"ora": "^4.0.3"
},
"devDependencies": {
"eslint": "^6.7.2"
}
}
复制代码
ESLint 的使用须要一个配置文件,建立 .eslintrc.js 文件(注意最前面有一个点),代码以下:
module.exports = {
"env": {
"es6": true,
"node": true,
},
"extends": "eslint:recommended",
};
复制代码
运行 npm start
,能够看到成功地运行了咱们的 timer.js 脚本;而运行 npm run lint
,没有输出任何结果(表明静态检查经过)。
npm scripts 看上去平淡无奇,可是却能为项目开发提供很是便利的工做流。例如,以前构建一个项目须要很是复杂的命令,可是若是你实现了一个 build
npm 脚本,那么当你的同事拿到这份代码时,只需简单地执行 npm run build
就能够开始构建,而无需关心背后的技术细节。在后续的 Node.js 或是前端学习中,咱们会在实际项目中使用各类 npm scripts 来定义咱们的工做流,你们慢慢就会领会到它的强大了。
在这篇教程的最后一节中,咱们将让你简单地感觉 Node 的事件机制。Node 的事件机制是比较复杂的,足够讲半本书,但这篇教程但愿能经过一个很是简单的实例,让你对 Node 事件有个初步的了解。
提示
若是你有过在网页(或其余用户界面)开发中编写事件处理(例如鼠标点击)的经验,那么你必定会以为 Node 中处理事件的方式似曾相识而又符合直觉。
咱们在前面简单地提了一下回调函数。实际上,回调函数和事件机制共同组成了 Node 的异步世界。具体而言,Node 中的事件都是经过 events
核心模块中的 EventEmitter
这个类实现的。EventEmitter
包括两个最关键的方法:
on
:用来监听事件的发生emit
:用来触发新的事件请看下面这个代码片断:
const EventEmitter = require('events').EventEmitter;
const emitter = new EventEmitter();
// 监听 connect 事件,注册回调函数
emitter.on('connect', function (username) {
console.log(username + '已链接');
});
// 触发 connect 事件,而且加上一个参数(即上面的 username)
emitter.emit('connect', '一只图雀');
复制代码
运行上面的代码,就会输出如下内容:
一只图雀已链接
复制代码
能够说,Node 中不少对象都继承自 EventEmitter
,包括咱们熟悉的 process
全局对象。在以前的 timer.js 脚本中,咱们监听 exit
事件(即 Node 进程结束),并添加一个自定义的回调函数打印“下次再见”的信息:
const program = require('commander');
const ora = require('ora');
const printProgramInfo = require('./info');
const datetime = require('./datetime');
program
.option('-t, --time <number>', '等待时间 (秒)', 3)
.option('-m, --message <string>', '要输出的信息', 'Hello World')
.parse(process.argv);
setTimeout(() => {
spinner.stop();
console.log(program.message);
}, program.time * 1000);
process.on('exit', () => {
console.log('下次再见~');
});
printProgramInfo();
console.log('当前时间', datetime.getCurrentTime());
const spinner = ora('正在加载中,请稍后 ...').start();
复制代码
运行后,会在程序退出后打印“下次再见~”的字符串。你可能会问,为啥不能在 setTimeout
的回调函数中添加程序退出的逻辑呢?由于除了正常运行结束(也就是等待了指定的时间),咱们的程序颇有可能会由于其余缘由退出(例如抛出异常,或者用 process.exit
强制退出),这时候经过监听 exit
事件,就能够在确保全部状况下都能执行 exit
事件的回调函数。若是你以为仍是不能理解的话,能够看下面这张示意图:
提示
process
对象还支持其余经常使用的事件,例如SIGINT
(用户按 Ctrl+C 时触发)等等,可参考这篇文档。
这篇 Node.js 快速入门教程到这里就结束了,但愿可以成为你进一步探索 Node.js 或是前端开发的基石。exit 事件已经触发,那咱们也下次再见啦~
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。