javascript開發後端程序的神器nodejs

簡介

javascript雖然一直均可以作服務端編程語言,可是它更多的是以客戶端編程語言來展現在世人面前的。也許javascript本身都忘記了還能夠作服務器端編程,直到2009年nodejs的橫空出世。javascript

nodejs的歷史

javascript做爲一門解釋性語言,是不須要像C或者C++那樣進行編譯的。可是在早期的時候,javascript引擎的執行效率是比較低的,因此致使javascript只能作作dom操做。java

隨着ajax的興起和現代web2.0的技術的發展,主流瀏覽器開發商儘量的提高javascript的執行效率,最後Chrome V8出現了,Chrome V8是 Chromium 項目開源的 JavaScript 引擎,使得javascript的執行效率獲得了極大的提高。node

nodejs藉着V8浴火重生了。web

nodejs從一誕生就得到了極大的關注。比較javascript的開發者仍是很是很是多的。並且一門語言能夠通用先後端是多麼的有吸引力。ajax

nodejs從2009年發展到2020年的nodejs 14,經歷了11年的歷史,和它的先輩javascript相比仍是很年輕,可是由於其開放性和包容性,nodejs在以一個很是快的速度向前發展。express

nodejs簡介

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

異步IO咱們你們應該都很清楚,和同步IO相比,線程不用阻塞,能夠去處理其餘更有意義的事情。只是在響應返回的時候恢復操做,因此不會浪費CPU時間。編程

咱們簡單看一下nodejs的IO模型:後端

一個好的語言須要有良好的生態系統相配合,由於語言自己只能提供最基本的一些操做,咱們還須要第三方系統來豐富這個語言的生態。api

而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 :

nodejs的框架

除了基本的nodejs以外,nodejs還有很是多優秀的框架,藉助這些框架咱們能夠是nodejs程序的搭建更加容易和強大。

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

本文做者:flydean程序那些事

本文連接:http://www.flydean.com/nodejs-kickoff/

本文來源:flydean的博客

歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

相關文章
相關標籤/搜索