您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關使用Nodejs怎么實現模塊化和事件循環,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
Node.js 到底是什么?開始學習的時候,對于前端的一些知識領域沒有太多的接觸(當然現在也一樣),對于 Node.js 的印象就是,它和Javascript 的語法幾乎一樣,然后是寫后端的。記得當時還竊喜,學了 Javascript = 啥都會了!好了切入正題
以前 Javascript 都是運行在瀏覽器上邊的,Javascript 是一種高級語言,計算機不能直接讀懂,畢竟二進制的計算機的世界里邊就只有010101...,在這個時候瀏覽器中的 JavaScript 引擎,就充當了翻譯官的角色,把 JavaScript 想要做什么手把手翻譯給計算機,這其中的編譯過程就不細說(我暫時也說不清楚)。
Node.js 基于 Chrome 瀏覽器的 V8 引擎,可以高效的編譯 Javascript,所以可以說 Node.js 是除瀏覽器以外的另一個 Javascript 運行環境。
記得在騰訊云的云函數上折騰過微信公眾號的簡單的自動回復,當時對前端代碼的模塊化有了小小的體會,Node.js 的功勞!
server.js 文件如下
// 引入 http 模塊 var http = require("http"); // 用 http 模塊創建服務 //req 獲取 url 信息 (request) //res 瀏覽器返回響應信息 (response) http.createServer(function (req, res) { // 設置 HTTP 頭部,狀態碼是 200,文件類型是 html,字符集是 utf8 //Content-Type字段用于定義網絡文件的類型和網頁的編碼,決定瀏覽器將以什么形式、什么編碼讀取這個文件,不寫就可能會出現亂碼哦 res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" }); // 往頁面打印值 res.write('小林別鬧'); // 結束響應 res.end(); }).listen(3000); // 監聽3000端口
安裝了 Node 的前提下在終端運行 node server.js
打開瀏覽器,在地址欄輸入http://localhost:3000/
就能看到頁面打印出來:
此時我們在本地搭建起一個最簡單的服務器,瀏覽器作為客戶端進行訪問
在上邊的代碼中,我們注意到了有 var http = require("http");
這樣的語句,作用是引入 http
模塊. 在 Node 中,模塊分為兩類:一是 Node 提供的模塊,稱為核心模塊;二是用戶編寫的模塊,稱為文件模塊.http
就是核心模塊之一,例如使用 http
模塊可以創建服務,path
模塊處理文件路徑,url
模塊用于處理與解析 URL.fs
模塊用于對系統文件及目錄進行讀寫操作等.
提到模塊化,就必須提一嘴 CommonJS,Node.js 就采用了部分 CommonJS 語法,可以理解為 CommonJS 是一種模塊化的標準.在早期為了解決通過script
標簽引入js
文件代碼產生的依賴順序易出錯,頂層作用域導致的變量污染等問題
在這里可以梳理一下導出 module.exports
和 exports
的差別
test2.js 如下:
let str = require('./test1'); console.log(str)
當 test1.js如下:
let str1 = '小林別鬧1' let str2 = '小林別鬧2' exports.str1 = str1 exports.str2 = str2 console.log(module.exports === exports)
在終端執行 node test2.js
結果如下:
/*輸出 { str1: '小林別鬧1', str2: '小林別鬧2' } true */ //改變test1.js文件變量暴露的方式 /* exports.str1 = str1 module.exports = str2 console.log(module.exports === exports) 輸出: false 小林別鬧2 */ /* exports.str1 = str1 module.exports.str2 = str2 console.log(module.exports === exports) 控制臺輸出: true { str1: '小林別鬧1', str2: '小林別鬧2' } */
可以進行一下總結:
在 Node 執行一個文件時,會給這個文件內生成一個 exports
對象和一個 module
對象,而這個module
對象又有一個屬性叫做 exports
,exports
是對 module.exports
的引用,它們指向同一塊地址,但是最終導出的是 module.exports
,第二次測試 module.exports = str2
改變了地址,所以 str1
沒有導出.
另外注意,使用 exports
導出是導出一個對象
Javascript 也是在不斷的發展進步,這不,Es6
版本就加入了Es Module
模塊
導出:
export const str1 = '小林別鬧1' export const str2 = '小林別鬧2' export default { fn() {}, msg: "小林別鬧" }
導入:
import { st1,str2,obj } from './test.js'
注意 import
,直接 node
js 文件執行會報錯的,需要 babel 編譯
比較一下的話就是:
CommonJs 可以動態加載語句,代碼發生在運行時
Es Module 是靜態的,不可以動態加載語句,只能聲明在該文件的最頂部,代碼發生在編譯時
在Node 中除了可以使用自己提供的核心模塊,自定義模塊,還可以使用第三方模塊
這就需要提到 npm
,npm
是 Node 的包管理工具,已經成為了世界上最大的開放源代碼的生態系統,我們可以下載各種包.
當然包管理工具還有yarn
,但是我暫時只用過 npm
,因為它隨 node
一起按照提供.
Java、PHP 或者 .NET 等服務端語言,會為每一個客戶端的連接創建一個新的線程。Node 不會為每一個客戶連接創建一個新的線程,而僅僅使用一個線程。
console.log('1') setTimeout(() => { console.log('2') }) console.log('3')//輸出132
Javascript 的代碼是從上到下一行行執行的,但是這里就不會阻塞,輸出3,再輸出2
Node 的事件循環真的好久才弄懂一丟丟,看過很多博客,覺得理解 Node 的事件循環機制,結合代碼及其運行結果來分析是最容易理解的。
libuv 庫負責 Node API 的執行。它將不同的任務分配給不同的線程,形成一個 Event Loop(事件循環),以異步的方式將任務的執行結果返回給 V8 引擎。其中 libuv 引擎中的事件循環分為 6 個階段,它們會按照順序反復運行。每當進入某一個階段的時候,都會從對應的回調隊列中取出函數去執行。當隊列為空或者執行的回調函數數量到達系統設定的閾值,就會進入下一階段。
console.log('start') setTimeout(() => {//定時器1 console.log('timer1') setTimeout(function timeout () {//定時器2 console.log('timeout'); },0); setImmediate(function immediate () {//定時器3 console.log('immediate'); }); Promise.resolve().then(function() { console.log('promise1') }) }, 0) setTimeout(() => {//定時器4 console.log('timer2') Promise.resolve().then(function() { console.log('promise2') }) }, 0) Promise.resolve().then(function() { console.log('promise3') }) console.log('end')
可以 Node 上邊運行一下
timers 階段:這個階段執行timer(setTimeout、setInterval)的回調
I/O callbacks 階段:處理一些上一輪循環中的少數未執行的 I/O 回調
idle, prepare 階段:僅node內部使用
poll 階段:獲取新的I/O事件, 適當的條件下node將阻塞在這里
check 階段:執行 setImmediate() 的回調
close callbacks 階段:執行 socket 的 close 事件回調
理解:首先執行同步任務,所以會輸出start end
,poll階段是事件循環的入口,有異步事件就是從這里進入的,同步任務執行完執行先微任務,輸出 promise3
,接下來就是 setTimeout
了,由 poll
階段一步步到 timers
階段,執行定時器1,輸出 timer1
,將定時器2和定時器3加入到隊列里邊,一旦執行一個階段里的一個任務就立刻執行微任務隊列,所以再輸出 promise1
,然后執行定時器4,如上輸出timer2,promise2
,結合事件再循環,到了 check
階段,執行 setImmediate() 的回調,輸出 immediate
,再循環進行,到達 timer
階段,輸出 timeout
瀏覽器和 Node 的事件循環是不一樣的
打算用兩張圖和一段代碼來解釋瀏覽器的事件循環機制,
console.log(1) setTimeout(()=>{console.log(2)},1000)//宏任務1 async function fn(){ console.log(3) setTimeout(()=>{console.log(4)},20)//宏任務2 //return Promise.reject()返回失敗狀態的,不會輸出6,弄不清楚為啥 return Promise.resolve() } async function run(){ console.log(5) await fn() //console.log(6), } run() //需要執行150ms左右 for(let i=0;i<90000000;i++){} setTimeout(()=>{//宏任務3 console.log(7) new Promise(resolve=>{ console.log(8) resolve() }).then(()=>{console.log(9)}) },0) console.log(10) // 1 5 3 10 4 7 8 9 2
執行結果如(請忽略我的工具提示):
我們可以儲備一些前置知識:JavaScript 是單線程的,任務可以分為同步任務和異步任務,像 console.log('1')
就是同步的,定時器 setTimeout
,promise
的回調等就是異步的。同步的很好理解,就從上到下一行一行的執行下來,異步的就有點小復雜了,還會分為宏任務和微任務。
瀏覽器的事件循環機制就是:先執行同步任務,同步任務執行完成,就執行任務隊列里面的任務,那任務隊列里面的任務是哪來的呢?異步任務準備好了就會放進任務隊列,你可以理解為,在任務隊列里邊宏任務和微任務都存在這一個隊列結構管著它們。先后的話,同步任務執行完成后,任務隊列里有微任務,則將微任務執行完,再執行一個宏任務,執行了宏任務可能又產生了微任務,這是就需要再執行完微任務任務。你可以將同步任務看成宏任務,這樣就可以理解為,每執行完一個宏任務都要清理一遍微任務。
上邊代碼解釋如下:執行到第一行代碼,輸出 1
,執行到第二行代碼 setTimeout
屬于宏任務1,準備1000毫秒后加入任務隊列,然后執行函數 run
,輸出 5
,因為 await
的存在,我們需要等待 fn
函數執行完畢,這里是通過 await
關鍵字將異步函數變成同步的,執行 fn
時輸出 3
,又出現一個 setTimeout
宏任務2,準備時間20毫秒,返回成功狀態的Promise
,輸出 6
,for
循環需要150ms,這是宏任務2,準備完畢,進入任務隊列,繼續向下,有一個 setTimeout
宏任務3,無需準備加入任務隊列,執行最后一行代碼,輸出 10
,至此同步任務全部執行完畢,接下來是異步任務了,任務隊列是隊列的數據結構,遵循先進先出的原則,此時任務隊列中有宏任務2和宏任務3,先執行宏任務2,輸出 4
,再執行宏任務3,輸出 7
,promise
本身是同步的,輸出 8
,回調then
里邊的代碼是微任務,宏任務3執行后,發現有微任務存在,清理一邊微任務,輸出 9
,整個流程經過1000毫秒后,宏任務1加入任務隊列,輸出 2
上述就是小編為大家分享的使用Nodejs怎么實現模塊化和事件循環了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。