溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

發(fā)布時(shí)間:2021-06-01 16:31:18 來源:億速云 閱讀:122 作者:Leah 欄目:web開發(fā)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

1 Node.js簡介

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 的功勞!

2 初體驗(yàn)

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/ 就能看到頁面打印出來:

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

此時(shí)我們在本地搭建起一個(gè)最簡單的服務(wù)器,瀏覽器作為客戶端進(jìn)行訪問

2.1 模塊化

在上邊的代碼中,我們注意到了有 var http = require("http"); 這樣的語句,作用是引入 http 模塊. 在 Node 中,模塊分為兩類:一是 Node 提供的模塊,稱為核心模塊;二是用戶編寫的模塊,稱為文件模塊.http 就是核心模塊之一,例如使用 http 模塊可以創(chuàng)建服務(wù),path 模塊處理文件路徑,url 模塊用于處理與解析 URL.fs模塊用于對系統(tǒng)文件及目錄進(jìn)行讀寫操作等.

2.1.1CommonJS

提到模塊化,就必須提一嘴 CommonJS,Node.js 就采用了部分 CommonJS 語法,可以理解為 CommonJS 是一種模塊化的標(biāo)準(zhǔn).在早期為了解決通過script標(biāo)簽引入js文件代碼產(chǎn)生的依賴順序易出錯(cuò),頂層作用域?qū)е碌淖兞课廴镜葐栴}

在這里可以梳理一下導(dǎo)出 module.exportsexports 的差別

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è)對象

2.1.2 Es Module

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í)

2.1.3 第三方模塊

在Node 中除了可以使用自己提供的核心模塊,自定義模塊,還可以使用第三方模塊

這就需要提到 npm ,npm 是 Node 的包管理工具,已經(jīng)成為了世界上最大的開放源代碼的生態(tài)系統(tǒng),我們可以下載各種包.

當(dāng)然包管理工具還有yarn,但是我暫時(shí)只用過 npm,因?yàn)樗S node 一起按照提供.

2.2  Node 的事件循環(huán)

2.2.1 非阻塞I/O

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

2.2.2事件循環(huán)

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)行一下

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huá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

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

2.2.3瀏覽器的事件循環(huán)

瀏覽器和 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é)果如(請忽略我的工具提示):

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

我們可以儲(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ù)。

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

使用Nodejs怎么實(shí)現(xiàn)模塊化和事件循環(huán)

上邊代碼解釋如下:執(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è)資訊頻道。

向AI問一下細(xì)節(jié)

免責(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)容。

AI