node.js的前世今生(特色篇)

前言

        在上一篇的文章裏我們見證了node的誕生和發展史知道了node是怎麼出現的,私下我查閱了很多資料和詢問了一些有經驗豐富的人在加上一些自己對node的理解,對整體有了一個稍微清晰的認識 希望通過這篇文章來和大家分享下node的特色 相互學習探討

 下面我們廢話不多說了直接進入正題

 

正文

接下來我們先看一個問題:

 

1.node是個什麼東西?

我們第一反應肯定是先去node官網看下官方大佬是如何定義的 我們打開官網可以看到

 

官方的意思是: node.js是建立在Chrome的V8 上面的一個JavaScript引擎

可能這麼說有點迷糊 我們換一個說法 我們知道node主要還是js代碼

我們原來在node還沒誕生的時候js代碼都是放在瀏覽器上面運行的

如今node大哥的出世,改變了瀏覽器武林盟主的地位,node大哥讓服務器端也可以運行js代碼(具體如何運行在下面的博文會做描述,這裏不做講解)

下面我們在通過學術一點的文字解釋:js是屬性腳本語言,然而腳本語言的運行都需要一個解析器。對於寫在頁面裏的js,瀏覽器充當瞭解析器的角色。而對於需要獨立運行的js,node就是那個解析器

通過上面的探討我們可以得出的結論是:node的出現就是給js這個小弟多了一種選擇,原來只能跟着瀏覽器混的纔可以發揮自己能力,現在跟着node一樣也可以(目前JavaScript的運行環境只有瀏覽器和node.js環境兩種

 

我們解決了第一個疑問知道了什麼是node

知道了它的一個特點是可以給js代碼提供一個運行環境

 

下面我們再來看第二個問題

2.node的主要特徵是什麼

我們可能從網上資料看到關於對node特徵評價詞中出現頻率最高無疑是 單線程非阻塞Io事件驅動

這幾個詞我們可能度都很熟悉但他們的結合就是組合node的特性下面我們來一一瞭解下這幾個特性

他們的具體工作原理詳解描述將在下篇node本質在那個描述

 

2.1單線程

注意:我這裏說的單線程只是js執行線程也就是主線程是單線程,當遇到I/O操作時會把操作交給專門執行I/O的線程工作

居然是單線程肯定是要和多線程比較

例如像java、php等這樣的後端語言,都是多線程的,當有一個請求過來的時候,開啓一個CPU,它使計算機能夠在同一時間執行多個線程  多線程同步模式是,將cpu分成幾個線程,每個線程同步運行。

而node的單線程特徵是指當遇到異步I/O請求的時候,它會將其放入I/O線程中執行("I/O" 主要指由libuv支持的,與系統磁盤和網絡之間的交互),待下一輪事件循環的時候再去判斷是否執行完成能否執行它的回調函數,若此時它的回調函數需要在加載I/O的話則將其在放入I/O線程中執行,它的特點是線程利用率是100%執行線程主線程一直循環檢測執行

 

雖然感覺使用單線程很好 不用考慮多線程 鎖 同步等問題,但我們還是理性看待一件事物的兩面性

單線程也有它自身的弱點,可靠性底下 不能充分利用多核cpu 單線程也就是程序執行時,程序必須是連續順序下來的,必須前面的處理好,後面的纔會執行到。   

這些弱點是學習Node的過程中必須要面對的。積極面對這些弱點,可以享受到Node帶來的好處,也能避免潛在的問題,使其可以高效利用

 

2.2非阻塞Io

和單線程一樣我們當然要和阻塞I/O進行對比學習

阻塞

阻塞 是指在 Node.js 程序中,其它 JavaScript 語句的執行,必須等待一個非 JavaScript 操作完成。這是因爲當 阻塞 發生時,事件循環無法繼續運行JavaScript。

在 Node.js 中,JavaScript 由於執行 CPU 密集型操作,而不是等待一個非 JavaScript 操作(例如I/O)而表現不佳,通常不被稱爲 阻塞。在 Node.js 標準庫中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模塊中也有 阻塞 方法。

在 Node.js 標準庫中的所有 I/O 方法都提供異步版本,非阻塞,並且接受回調函數。某些方法也有對應的 阻塞 版本,名字以 Sync 結尾。

代碼比較

阻塞 方法 同步 執行,非阻塞 方法 異步 執行。

使用文件系統模塊做例子,這是一個 同步 文件讀取:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 在這裏阻塞直到文件被讀取

這是一個等同的 異步 示例:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
});

第一個示例看上去比第二個簡單些,但是有一個缺陷:第二行語句會 阻塞 其它 JavaScript 語句的執行直到整個文件全部讀取完畢。注意在同步版本中,如果錯誤被拋出,它需要被捕獲否則整個程序都會崩潰。在異步版本中,由作者來決定錯誤是否如上所示拋出。

讓我們稍微擴展一下我們的例子:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 在這裏阻塞直到文件被讀取
console.log(data);
// moreWork(); 在console.log之後執行

這是一個類似但不等同的異步示例:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
// moreWork(); 在console.log之前執行

在上述第一個例子中, console.log 將在 moreWork() 之前被調用。在第二個例子中, fs.readFile() 是 非阻塞 的,所以 JavaScript 執行可以繼續, moreWork() 將被首先調用。在不等待文件讀取完成的情況下運行 moreWork()

通過上面的說明和代碼實例我們可以看出node使用非阻塞I/O可以大大提高系統吞吐量

 

2.2事件驅動

事件驅動(event-driven)node的一大特性也是node很核心的存在。通俗一點描述就是

通過監聽事件的狀態變化來做出相應的操作

比如讀取一個文件,文件讀取完畢,或者文件讀取錯誤,那麼就觸發對應的狀態,然後調用對應的回掉函數來進行處理

那麼我們知道爲什麼node要採用事件驅動的方式啦

通俗一點來書就是:前世的因 今生的果 凡事皆有因果 (不開玩笑啦)

其實上面這句話說得也沒毛病在我的上一篇博客裏面講述了node的誕生知道node基於js語言然而

js是一種很棒的事件驅動編程語言,因爲它允許使用匿名函數和閉包,更重要的是,任何寫過代碼的人都熟悉它的語法。事件發生時調用的回調函數可以在捕獲事件處進行編寫。這樣可以使代碼容易編寫和維護,沒有複雜的面向對象框架,沒有接口,沒有過度設計的可能性。只需監聽事件,編寫一個回調函數,其他事情都可以交給系統處理 所以我們的老大哥node也選擇這種方式

當然這不是全部原因單線程等其他性質也決定了他的方式(其實就是不想麻煩我們開發者啊 不用去關心一些其他亂七八糟的東西)

 

我們現在知道了node爲什麼要選擇面向事件驅動編程而不選擇面向對象編程的原由:

那麼我們下面先簡單敘述一下這個過程(具體細節將在下篇文一一講解)

因爲node單線程運行的,通過一個事件循環(event-loop)來循環取出消息隊列(event-queue)中的消息進行處理,

處理過程基本上就是去調用該消息對應的回調函數。

消息隊列就是當一個事件狀態發生變化時,就將一個消息壓入隊列中

 

總結

    本文描述了node的幾大特徵  單線程非阻塞Io事件驅動

    講述了他們的原由和基本理解,在寫博客的過程中通過對各個知識點的熟悉查閱 有了更深的理解

     如文中有什麼錯誤問題或有疑問的地方都可指出 大家相互學習  

 

    下篇node.js本質核心將會對這幾大特徵

      進行更深入的介紹和node其他核心點,對node有一個更全面更深入的理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章