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