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 事件已經觸發,那咱們也下次再見啦~
想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。