天天看點

javascript開發後端程式的神器nodejs簡介nodejs的曆史nodejs簡介nodejs的運作環境processexports子產品nodejs APInodejs的架構

簡介

javascript雖然一直都可以做服務端程式設計語言,但是它更多的是以用戶端程式設計語言來展示在世人面前的。也許javascript自己都忘記了還可以做伺服器端程式設計,直到2009年nodejs的橫空出世。

nodejs的曆史

javascript作為一門解釋性語言,是不需要像C或者C++那樣進行編譯的。但是在早期的時候,javascript引擎的執行效率是比較低的,是以導緻javascript隻能做做dom操作。

随着ajax的興起和現代web2.0的技術的發展,主流浏覽器開發商盡可能的提升javascript的執行效率,最後Chrome V8出現了,Chrome V8是 Chromium 項目開源的 JavaScript 引擎,使得javascript的執行效率得到了極大的提升。

nodejs借着V8浴火重生了。

nodejs從一誕生就獲得了極大的關注。比較javascript的開發者還是非常非常多的。而且一門語言可以通用前後端是多麼的有吸引力。

nodejs從2009年發展到2020年的nodejs 14,經曆了11年的曆史,和它的先輩javascript相比還是很年輕,但是因為其開放性和包容性,nodejs在以一個非常快的速度向前發展。

nodejs簡介

nodejs借助于V8引擎和一組異步的 I/O 原生功能,極大的提升了nodejs的處理效率。

異步IO我們大家應該都很清楚,和同步IO相比,線程不用阻塞,可以去處理其他更有意義的事情。隻是在響應傳回的時候恢複操作,是以不會浪費CPU時間。

我們簡單看一下nodejs的IO模型:

javascript開發後端程式的神器nodejs簡介nodejs的曆史nodejs簡介nodejs的運作環境processexports子產品nodejs APInodejs的架構

一個好的語言需要有良好的生态系統相配合,因為語言本身隻能提供最基本的一些操作,我們還需要第三方系統來豐富這個語言的生态。

而nodejs的npm倉庫,托管着全球最大的開源庫生态系統。

基本上使用nodejs你可以實作絕大多數需要的功能。

nodejs的另外一個特點就是簡單,考慮一下我們最常用的web應用,如果用java來寫,非常麻煩,你還需要一個web伺服器。

在nodejs中,一切都是那麼的簡單:

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('welcome to www.flydean.com\n')
})

server.listen(port, hostname, () => {
  console.log(`please visit http://${hostname}:${port}/`)
})           

上面的代碼就建立了一個web服務,監聽在3000端口,

我們首先引入了http子產品,用來進行http處理。

接着使用http 的 createServer() 方法會建立新的 HTTP 伺服器并傳回它。

在createServer方法内部,我們可以設定要傳回的對象。

最後啟用server.listen功能,來監聽特定的端口和伺服器,當服務就緒之後,會調用後面的回調函數,執行特定的指令。

每當接收到新的請求的時候,就會觸發request事件,request事件可以傳遞兩個參數:

  • request 是一個http.IncomingMessage對象,提供了請求的詳細資訊。
  • response 是一個http.ServerResponse對象,用于傳回資料給調用方。

在上面的例子中,我們并沒有使用request,而是使用response直接建構了傳回的對象。

我們設定了statusCode和header,最後使用end來關閉響應。

這就是一個簡單使用的nodejs程式。

nodejs的運作環境

nodejs作為js的一種,是一種解釋性語言,一般解釋性語言都有兩種運作方式。

一種是直接運作,一種是開啟一個解釋性的環境,在其中運作,nodejs也不例外。

直接運作很簡單,我們寫好nodejs的程式之後,比如app.js,直接這樣運作:

node app.js           

如果直接執行node指令,就會開啟REPL模式:

node
Welcome to Node.js v12.13.1.
Type ".help" for more information.
>           

REPL 也被稱為運作評估列印循環,是一種程式設計語言環境(主要是控制台視窗),它使用單個表達式作為使用者輸入,并在執行後将結果傳回到控制台。

REPL有什麼作用呢?

第一,我們可以直接在REPL中運作某些測試方法,已驗證輸出結果。

比如這樣:

> console.log('www.flydean.com');
www.flydean.com           

除此之外REPL還有一些更加有用的功能,我們知道JS中一切皆對象,比如上面我們提到的http對象,如果我們想知道http對象的大概結構怎麼辦呢?

直接在REPL環境中輸入http即可:

> http
{
  _connectionListener: [Function: connectionListener],
  METHODS: [
    'ACL',         'BIND',       'CHECKOUT',
    'CONNECT',     'COPY',       'DELETE',
    'GET',         'HEAD',       'LINK',
    'LOCK',        'M-SEARCH',   'MERGE',
    'MKACTIVITY',  'MKCALENDAR', 'MKCOL',
    'MOVE',        'NOTIFY',     'OPTIONS',
    'PATCH',       'POST',       'PROPFIND',
    'PROPPATCH',   'PURGE',      'PUT',
    'REBIND',      'REPORT',     'SEARCH',
    'SOURCE',      'SUBSCRIBE',  'TRACE',
    'UNBIND',      'UNLINK',     'UNLOCK',
    'UNSUBSCRIBE'
  ],
  STATUS_CODES: {
    '100': 'Continue',
    '101': 'Switching Protocols',
    '102': 'Processing',
    '103': 'Early Hints',
    '200': 'OK',
    '201': 'Created',
    '202': 'Accepted',
    '203': 'Non-Authoritative Information',
    '204': 'No Content',
    '205': 'Reset Content',
    '206': 'Partial Content',
    '207': 'Multi-Status',
    '208': 'Already Reported',
    '226': 'IM Used',
    '300': 'Multiple Choices',
    '301': 'Moved Permanently',
    '302': 'Found',
    '303': 'See Other',
    '304': 'Not Modified',
    '305': 'Use Proxy',
    '307': 'Temporary Redirect',
    '308': 'Permanent Redirect',
    '400': 'Bad Request',
    '401': 'Unauthorized',
    '402': 'Payment Required',
    '403': 'Forbidden',
    '404': 'Not Found',
    '405': 'Method Not Allowed',
    '406': 'Not Acceptable',
    '407': 'Proxy Authentication Required',
    '408': 'Request Timeout',
    '409': 'Conflict',
    '410': 'Gone',
    '411': 'Length Required',
    '412': 'Precondition Failed',
    '413': 'Payload Too Large',
    '414': 'URI Too Long',
    '415': 'Unsupported Media Type',
    '416': 'Range Not Satisfiable',
    '417': 'Expectation Failed',
    '418': "I'm a Teapot",
    '421': 'Misdirected Request',
    '422': 'Unprocessable Entity',
    '423': 'Locked',
    '424': 'Failed Dependency',
    '425': 'Unordered Collection',
    '426': 'Upgrade Required',
    '428': 'Precondition Required',
    '429': 'Too Many Requests',
    '431': 'Request Header Fields Too Large',
    '451': 'Unavailable For Legal Reasons',
    '500': 'Internal Server Error',
    '501': 'Not Implemented',
    '502': 'Bad Gateway',
    '503': 'Service Unavailable',
    '504': 'Gateway Timeout',
    '505': 'HTTP Version Not Supported',
    '506': 'Variant Also Negotiates',
    '507': 'Insufficient Storage',
    '508': 'Loop Detected',
    '509': 'Bandwidth Limit Exceeded',
    '510': 'Not Extended',
    '511': 'Network Authentication Required'
  },
  Agent: [Function: Agent] { defaultMaxSockets: Infinity },
  ClientRequest: [Function: ClientRequest],
  IncomingMessage: [Function: IncomingMessage],
  OutgoingMessage: [Function: OutgoingMessage],
  Server: [Function: Server],
  ServerResponse: [Function: ServerResponse],
  createServer: [Function: createServer],
  get: [Function: get],
  request: [Function: request],
  maxHeaderSize: [Getter],
  globalAgent: [Getter/Setter]
}           

直接輸出了http對象的簡潔結構,我們還可以使用tab按鈕來自動補全http的方法:

> http.
http.__defineGetter__      http.__defineSetter__      http.__lookupGetter__      http.__lookupSetter__      http.__proto__             http.constructor
http.hasOwnProperty        http.isPrototypeOf         http.propertyIsEnumerable  http.toLocaleString        http.toString              http.valueOf

http.Agent                 http.ClientRequest         http.IncomingMessage       http.METHODS               http.OutgoingMessage       http.STATUS_CODES
http.Server                http.ServerResponse        http._connectionListener   http.createServer          http.get                   http.globalAgent
http.maxHeaderSize         http.request           

PREL還支援一些特定的點操作:

> .help
.break    Sometimes you get stuck, this gets you out
.clear    Alias for .break
.editor   Enter editor mode
.exit     Exit the repl
.help     Print this help message
.load     Load JS from a file into the REPL session
.save     Save all evaluated commands in this REPL session to a file           
PERL還有一個特殊變量 _ ,如果在某些代碼之後輸入 _,則會列印最後一次操作的結果。

process

process 對象是一個全局變量,提供了有關目前 Node.js 程序的資訊并對其進行控制。 作為全局變量,它始終可供 Node.js 應用程式使用,無需使用 require()。 它也可以使用 require() 顯式地通路。

因為process代表的是nodejs的程序資訊,是以可以處理程序終止,讀取環境變量,接收指令行參數等作用。

終止程序

先看一下怎麼使用process來終止程序:

process.exit(0)           

0表示正常退出,當然,我們可以傳入不同的退出碼,表示不同的含義。

正常情況下,如果沒有異步操作正在等待,那麼 Node.js 會以狀态碼 0 退出,其他情況下,會用如下的狀态碼:

1 未捕獲異常 - 一個未被捕獲的異常, 并且沒被 domain 或 'uncaughtException' 事件處理器處理。

2 - 未被使用 (Bash 為防内部濫用而保留)

3 内部的 JavaScript 解析錯誤 - Node.js 内部的 JavaScript 源代碼在引導程序中導緻了一個文法解析錯誤。一般隻會在開發 Node.js 本身的時候出現。

4 内部的 JavaScript 執行失敗 - 引導程序執行 Node.js 内部的 JavaScript 源代碼時,傳回函數值失敗。一般隻會在開發 Node.js 本身的時候出現。

5 緻命錯誤 - 在 V8 中有一個緻命的錯誤。 比較典型的是以 FATALERROR 為字首從 stderr 列印出來的消息。

6 非函數的内部異常處理 - 發生了一個内部異常,但是内部異常處理函數被設定成了一個非函數,或者不能被調用。

7 内部異常處理運作時失敗 - 有一個不能被捕獲的異常,在試圖處理這個異常時,處理函數本身抛出了一個錯誤。比如, 如果一個 'uncaughtException' 或者 domain.on('error') 處理函數抛出了一個錯誤。

8 - 未被使用,在之前版本的 Node.js, 退出碼 8 有時候表示一個未被捕獲的異常。

9 - 不可用參數 - 某個未知選項沒有确定,或者沒給必需要的選項填值。

10 内部的 JavaScript 運作時失敗 - 調用引導函數時,引導程序執行 Node.js 内部的 JavaScript 源代碼抛出錯誤。 一般隻會在開發 Node.js 本身的時候出現。

12 不可用的調試參數

13 未完成的Top-Level Await: await傳入的Promise一直沒有調用resolve方法

128 退出信号 - 如果 Node.js 接收到緻命信号, 諸如 SIGKILL 或 SIGHUP,那麼它的退出代碼将是 128 加上信号的碼值。 例如,信号 SIGABRT 的值為 6,是以預期的退出代碼将為 128 + 6 或 134。

我們可以通過process的on方法,來監聽信号事件:

process.on('SIGTERM', () => {
  server.close(() => {
    console.log('程序已終止')
  })
})           
什麼是信号?信号是一個 POSIX 内部通信系統:發送通知給程序,以告知其發生的事件。

或者我們可以從程式内部發送這個信号:

process.kill(process.pid, 'SIGTERM')           

env

因為process程序是和外部環境打交道的,process提供了env屬性,該屬性承載了在啟動程序時設定的所有環境變量。

預設情況下,env中的NODE_ENV被設定為development。

process.env.NODE_ENV // "development"           

我們可以通過修改這個環境變量,來切換nodejs的不同運作環境。

argv

process提供了argv來接收外部參數。

比如:

node app.js joe           

argv是一個包含所有指令行調用參數的數組。

上面的例子中,第一個參數是 node 指令的完整路徑。第二個參數是正被執行的檔案的完整路徑。所有其他的參數從第三個位置開始。

要想擷取joe,我們可以這樣做:

const args = process.argv.slice(2)
args[0]           

如果是key=value的情況,我們可以這樣傳參數,并且使用minimist 庫來處理參數:

node app.js --name=joe

const args = require('minimist')(process.argv.slice(2))
args['name'] //joe           

CLI互動

從 nodejs7開始,nodejs提供了readline子產品,可以從process.stdin擷取輸入:

const readline = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
})

readline.question(`how are you?`, answer => {
  console.log(`${answer}!`)
  readline.close()
})           

如果需要更加複雜的操作,則可以使用Inquirer.js:

const inquirer = require('inquirer')

var questions = [
  {
    type: 'input',
    name: 'hello',
    message: "how are you?"
  }
]

inquirer.prompt(questions).then(answers => {
  console.log(`${answers['hello']}!`)
})           

exports子產品

nodejs擁有内置的子產品系統,當我們需要使用其他lib提供的功能時候,我們可以使用require來引入其他lib公開的子產品。

但是前提是該lib需要公開,也就是exports對應的子產品出來。

nodejs的對象導出有兩種方式module.exports和将對象添加為 exports 的屬性。

先看第一種方式,square 子產品定義在 square.js 中:

module.exports = class Square {
  constructor(width) {
    this.width = width;
  }

  area() {
    return this.width ** 2;
  }
};           

下面的例子中, bar.js 使用了導出 Square 類的 square 子產品:

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`mySquare 的面積是 ${mySquare.area()}`);           

再看第二種方式,定義一個circle.js:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r;           

使用:

const circle = require('./circle.js');
console.log(`半徑為 4 的圓的面積是 ${circle.area(4)}`);           

兩者都可以導出特定的子產品,但是module.exports隻會導出特定的對象,而exports是将對象添加為exports的屬性,我們還需要根據屬性名稱來查找對象的屬性。

nodejs API

除了我們上面提到的http,process, nodejs還提供了很多其他非常有用的API :

javascript開發後端程式的神器nodejs簡介nodejs的曆史nodejs簡介nodejs的運作環境processexports子產品nodejs APInodejs的架構

nodejs的架構

除了基本的nodejs之外,nodejs還有非常多優秀的架構,借助這些架構我們可以是nodejs程式的搭建更加容易和強大。

像AdonisJs,express,koa,Socket.io等等。

本文作者:flydean程式那些事

本文連結:

http://www.flydean.com/nodejs-kickoff/

本文來源:flydean的部落格

歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!