首頁 > web前端 > js教程 > 主體

聊聊Nodejs中的模組化和事件循環

青灯夜游
發布: 2021-07-01 19:08:43
轉載
2028 人瀏覽過

本篇文章帶大家來了解Nodejs中的模組化和事件循環。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

聊聊Nodejs中的模組化和事件循環

5.20出了線上Ide,能夠在瀏覽器上邊執行Node.js —WebContainers

1 Node.js簡介

Node.js 到底是什麼?開始學習的時候,對於前端的一些知識領域沒有太多的接觸(當然現在也一樣),對於 Node.js 的印象就是,它和Javascript 的語法幾乎一樣,然後是寫後端的。記得當時還竊喜,學了 Javascript = 啥都會了!好了切入正題

【推薦學習:《nodejs 教程》】

以前Javascript 都是運行在瀏覽器上邊的,Javascript 是一種高級語言,計算機不能直接讀懂,畢竟二進制的計算機的世界裡邊就只有010101...,在這個時候瀏覽器中的JavaScript 引擎,就充當了翻譯官的角色,把JavaScript 想要做什麼手把手翻譯給計算機,這其中的編譯過程就不細說(我暫時也說不清楚)。

Node.js 基於 Chrome 瀏覽器的 V8 引擎,可以有效率的編譯 Javascript,所以可以說 Node.js 是瀏覽器以外的另一個 Javascript 運作環境。

記得在騰訊雲的雲函數上折騰過微信公眾號的簡單的自動回复,當時對前端代碼的模組化有了小小的體會,Node.js 的功勞!

2 初步體驗

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

聊聊Nodejs中的模組化和事件循環

##此時我們在本地搭建起一個最簡單的伺服器,瀏覽器作為客戶端進行訪問

2.1 模組化

在上邊的程式碼中,我們注意到了有

var http = require("http"); 這樣的語句,作用是引入http 模組. 在Node 中,模組分為兩類:一是Node 提供的模組,稱為核心模組;二是使用者編寫的模組,稱為檔案模組.http 就是核心模組之一,例如使用http 模組可以創建服務,path 模組處理檔案路徑,url 模組用於處理與解析URL.fs模組用於對系統檔案及目錄進行讀寫操作等.

2.1. 1CommonJS

提到模組化,就必須提一嘴CommonJS,Node.js 就採用了部分CommonJS 語法,可以理解為CommonJS 是一種模組化的標準.在早期為了解決通過

script標籤引入js檔案程式碼產生的依賴順序易出錯,頂層作用域導致的變數污染等問題

在這裡可以梳理一下導出

module. exportsexports 的差異

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 導出是導出一個物件

2.1.2 Es Module

#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 是靜態的,不可以動態載入語句,只能宣告在該檔案的最頂部,程式碼發生在編譯時

#2.1.3 第三方模組

在Node 中除了可以使用自己提供的核心模組,自訂模組,還可以使用

第三方模組

这就需要提到 npm ,npm 是 Node 的包管理工具,已经成为了世界上最大的开放源代码的生态系统,我们可以下载各种包.

当然包管理工具还有yarn,但是我暂时只用过 npm,因为它随 node 一起按照提供.

2.2 Node 的事件循环

2.2.1 非阻塞I/O

Java、PHP 或者 .NET 等服务端语言,会为每一个客户端的连接创建一个新的线程。Node 不会为每一个客户连接创建一个新的线程,而仅仅使用一个线程。

console.log('1')
setTimeout(() => {
  console.log('2')
})
console.log('3')//输出132
登入後複製

Javascript 的代码是从上到下一行行执行的,但是这里就不会阻塞,输出3,再输出2

2.2.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 上边运行一下

聊聊Nodejs中的模組化和事件循環

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

聊聊Nodejs中的模組化和事件循環

2.2.3浏览器的事件循环

浏览器和 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
登入後複製

执行结果如(请忽略我的工具提示):

聊聊Nodejs中的模組化和事件循環

我们可以储备一些前置知识:JavaScript 是单线程的,任务可以分为同步任务和异步任务,像 console.log('1') 就是同步的,定时器 setTimeout,promise 的回调等就是异步的。同步的很好理解,就从上到下一行一行的执行下来,异步的就有点小复杂了,还会分为宏任务和微任务。

浏览器的事件循环机制就是:先执行同步任务,同步任务执行完成,就执行任务队列里面的任务,那任务队列里面的任务是哪来的呢?异步任务准备好了就会放进任务队列,你可以理解为,在任务队列里边宏任务和微任务都存在这一个队列结构管着它们。先后的话,同步任务执行完成后,任务队列里有微任务,则将微任务执行完,再执行一个宏任务,执行了宏任务可能又产生了微任务,这是就需要再执行完微任务任务。你可以将同步任务看成宏任务,这样就可以理解为,每执行完一个宏任务都要清理一遍微任务。

聊聊Nodejs中的模組化和事件循環

聊聊Nodejs中的模組化和事件循環

上邊程式碼解釋如下:執行到第一行程式碼,輸出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,輸出7promise本身是同步的,輸出8,回呼then 裡邊的程式碼是微任務,巨集任務3執行後,發現有微任務存在,清理一邊微任務,輸出9,整個流程經過1000毫秒後,巨集任務1加入任務佇列,輸出2

這裡直接看一位大哥的部落格可能更容易理解,寫不了那麼好,哭,連結放在下面了。

寫的不好,羞愧萬分,會努力學習的! ! !

更多程式相關知識,請造訪:程式設計影片! !

以上是聊聊Nodejs中的模組化和事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!