真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JavaScript事件循環(huán)的原理是什么

今天小編給大家分享一下JavaScript事件循環(huán)的原理是什么的相關(guān)知識點,內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

成都創(chuàng)新互聯(lián)公司服務(wù)項目包括云龍網(wǎng)站建設(shè)、云龍網(wǎng)站制作、云龍網(wǎng)頁制作以及云龍網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,云龍網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到云龍省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

理解 JavaScript 的事件循環(huán)往往伴隨著宏任務(wù)和微任務(wù)、JavaScript 單線程執(zhí)行過程及瀏覽器異步機(jī)制等相關(guān)問題,而瀏覽器和  NodeJS  中的事件循環(huán)實現(xiàn)也是有很大差別。熟悉事件循環(huán),了解瀏覽器運行機(jī)制將對我們理解 JavaScript 的執(zhí)行過程,以及在排查代碼運行問題時有很大幫助。

瀏覽器 JS 異步執(zhí)行的原理

JS 是單線程的,也就是同一個時刻只能做一件事情,那么思考:為什么瀏覽器可以同時執(zhí)行異步任務(wù)呢?

因為瀏覽器是多線程的,當(dāng) JS 需要執(zhí)行異步任務(wù)時,瀏覽器會另外啟動一個線程去執(zhí)行該任務(wù)。也就是說,“JS 是單線程的”指的是執(zhí)行 JS 代碼的線程只有一個,是瀏覽器提供的 JS 引擎線程(主線程)。瀏覽器中還有定時器線程和 HTTP 請求線程等,這些線程主要不是來跑 JS 代碼的。

比如主線程中需要發(fā)一個 AJAX 請求,就把這個任務(wù)交給另一個瀏覽器線程(HTTP 請求線程)去真正發(fā)送請求,待請求回來了,再將 callback 里需要執(zhí)行的 JS 回調(diào)交給 JS 引擎線程去執(zhí)行。**即瀏覽器才是真正執(zhí)行發(fā)送請求這個任務(wù)的角色,而 JS 只是負(fù)責(zé)執(zhí)行最后的回調(diào)處理。**所以這里的異步不是 JS 自身實現(xiàn)的,其實是瀏覽器為其提供的能力。

JavaScript事件循環(huán)的原理是什么

以 Chrome 為例,瀏覽器不僅有多個線程,還有多個進(jìn)程,如渲染進(jìn)程、GPU 進(jìn)程和插件進(jìn)程等。而每個 tab 標(biāo)簽頁都是一個獨立的渲染進(jìn)程,所以一個 tab 異常崩潰后,其他 tab 基本不會被影響。作為前端開發(fā)者,主要重點關(guān)注其渲染進(jìn)程,渲染進(jìn)程下包含了 JS 引擎線程、HTTP 請求線程和定時器線程等,這些線程為 JS 在瀏覽器中完成異步任務(wù)提供了基礎(chǔ)。

JavaScript事件循環(huán)的原理是什么

事件驅(qū)動淺析

瀏覽器異步任務(wù)的執(zhí)行原理背后其實是一套事件驅(qū)動的機(jī)制。事件觸發(fā)、任務(wù)選擇和任務(wù)執(zhí)行都是由事件驅(qū)動機(jī)制來完成的。NodeJS 和瀏覽器的設(shè)計都是基于事件驅(qū)動的,簡而言之就是由特定的事件來觸發(fā)特定的任務(wù),這里的事件可以是用戶的操作觸發(fā)的,如 click 事件;也可以是程序自動觸發(fā)的,比如瀏覽器中定時器線程在計時結(jié)束后會觸發(fā)定時器事件。而本文的主題內(nèi)容事件循環(huán)其實就是在事件驅(qū)動模式中來管理和執(zhí)行事件的一套流程。

以一個簡單場景為例,假設(shè)游戲界面上有一個移動按鈕和人物模型,每次點擊右移后,人物模型的位置需要重新渲染,右移 1 像素。根據(jù)渲染時機(jī)的不同我們可以用不同的方式來實現(xiàn)。

JavaScript事件循環(huán)的原理是什么

實現(xiàn)方式一:事件驅(qū)動。點擊按鈕后,修改坐標(biāo) positionX 時,立即觸發(fā)界面渲染的事件,觸發(fā)重新渲染。

實現(xiàn)方式二:狀態(tài)驅(qū)動或數(shù)據(jù)驅(qū)動。點擊按鈕后,只修改坐標(biāo) positionX,不觸發(fā)界面渲染。在此之前會啟動一個定時器 setInterval,或者利用 requestAnimationFrame 來不斷地檢測 positionX 是否有變化。如果有變化,則立即重新渲染。

瀏覽器中的點擊事件處理也是典型的基于事件驅(qū)動。在事件驅(qū)動中,當(dāng)有事件觸發(fā)后,被觸發(fā)的事件會按順序暫時存在一個隊列中,待 JS 的同步任務(wù)執(zhí)行完成后,會從這個隊列中取出要處理的事件并進(jìn)行處理。那么具體什么時候取任務(wù)、優(yōu)先取哪些任務(wù),這就由事件循環(huán)流程來控制了。

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

執(zhí)行棧與任務(wù)隊列

JS 在解析一段代碼時,會將同步代碼按順序排在某個地方,即執(zhí)行棧,然后依次執(zhí)行里面的函數(shù)。當(dāng)遇到異步任務(wù)時就交給其他線程處理,待當(dāng)前執(zhí)行棧所有同步代碼執(zhí)行完成后,會從一個隊列中去取出已完成的異步任務(wù)的回調(diào)加入執(zhí)行棧繼續(xù)執(zhí)行,遇到異步任務(wù)時又交給其他線程,.....,如此循環(huán)往復(fù)。而其他異步任務(wù)完成后,將回調(diào)放入任務(wù)隊列中待執(zhí)行棧來取出執(zhí)行。

JS 按順序執(zhí)行執(zhí)行棧中的方法,每次執(zhí)行一個方法時,會為這個方法生成獨有的執(zhí)行環(huán)境(上下文 context),待這個方法執(zhí)行完成后,銷毀當(dāng)前的執(zhí)行環(huán)境,并從棧中彈出此方法(即消費完成),然后繼續(xù)下一個方法。

JavaScript事件循環(huán)的原理是什么

可見,在事件驅(qū)動的模式下,至少包含了一個執(zhí)行循環(huán)來檢測任務(wù)隊列是否有新的任務(wù)。通過不斷循環(huán)去取出異步回調(diào)來執(zhí)行,這個過程就是事件循環(huán),而每一次循環(huán)就是一個事件周期或稱為一次 tick。

宏任務(wù)和微任務(wù)

任務(wù)隊列不只一個,根據(jù)任務(wù)的種類不同,可以分為微任務(wù)(micro task)隊列和宏任務(wù)(macro task)隊列。

事件循環(huán)的過程中,執(zhí)行棧在同步代碼執(zhí)行完成后,優(yōu)先檢查微任務(wù)隊列是否有任務(wù)需要執(zhí)行,如果沒有,再去宏任務(wù)隊列檢查是否有任務(wù)執(zhí)行,如此往復(fù)。微任務(wù)一般在當(dāng)前循環(huán)就會優(yōu)先執(zhí)行,而宏任務(wù)會等到下一次循環(huán),因此,微任務(wù)一般比宏任務(wù)先執(zhí)行,并且微任務(wù)隊列只有一個,宏任務(wù)隊列可能有多個。另外我們常見的點擊和鍵盤等事件也屬于宏任務(wù)。

下面我們看一下常見宏任務(wù)和常見微任務(wù)。

常見宏任務(wù):

  • setTimeout()

  • setInterval()

  • setImmediate()

常見微任務(wù):

  • promise.then()、promise.catch()

  • new MutaionObserver()

  • process.nextTick()

console.log('同步代碼1');setTimeout(() => {    console.log('setTimeout')
}, 0)new Promise((resolve) => {  console.log('同步代碼2')  resolve()
}).then(() => {    console.log('promise.then')
})console.log('同步代碼3');// 最終輸出"同步代碼1"、"同步代碼2"、"同步代碼3"、"promise.then"、"setTimeout"

上面的代碼將按如下順序輸出為:"同步代碼 1"、"同步代碼 2"、"同步代碼 3"、"promise.then"、"setTimeout",具體分析如下。

(1)setTimeout 回調(diào)和 promise.then 都是異步執(zhí)行的,將在所有同步代碼之后執(zhí)行;

順便提一下,在瀏覽器中 setTimeout 的延時設(shè)置為 0 的話,會默認(rèn)為 4ms,NodeJS 為 1ms。具體值可能不固定,但不是為 0。

(2)雖然 promise.then 寫在后面,但是執(zhí)行順序卻比 setTimeout 優(yōu)先,因為它是微任務(wù);

(3)new Promise 是同步執(zhí)行的,promise.then 里面的回調(diào)才是異步的。

下面我們看一下上面代碼的執(zhí)行過程演示:

JavaScript事件循環(huán)的原理是什么

也有人這樣去理解:微任務(wù)是在當(dāng)前事件循環(huán)的尾部去執(zhí)行;宏任務(wù)是在下一次事件循環(huán)的開始去執(zhí)行。我們來看看微任務(wù)和宏任務(wù)的本質(zhì)區(qū)別是什么。

我們已經(jīng)知道,JS 遇到異步任務(wù)時會將此任務(wù)交給其他線程去處理,自己的主線程繼續(xù)往后執(zhí)行同步任務(wù)。比如 setTimeout 的計時會由瀏覽器的定時器線程來處理,待計時結(jié)束,就將定時器回調(diào)任務(wù)放入任務(wù)隊列等待主線程來取出執(zhí)行。前面我們提到,因為 JS 是單線程執(zhí)行的,所以要執(zhí)行異步任務(wù),就需要瀏覽器其他線程來輔助,即多線程是 JS 異步任務(wù)的一個明顯特征。

我們再來分析下 promise.then(微任務(wù))的處理。當(dāng)執(zhí)行到 promise.then 時,V8 引擎不會將異步任務(wù)交給瀏覽器其他線程,而是將回調(diào)存在自己的一個隊列中,待當(dāng)前執(zhí)行棧執(zhí)行完成后,立馬去執(zhí)行 promise.then 存放的隊列,promise.then 微任務(wù)沒有多線程參與,甚至從某些角度說,微任務(wù)都不能完全算是異步,它只是將書寫時的代碼修改了執(zhí)行順序而已。

setTimeout 有“定時等待”這個任務(wù),需要定時器線程執(zhí)行;ajax 請求有“發(fā)送請求”這個任務(wù),需要 HTTP 線程執(zhí)行,而 promise.then 它沒有任何異步任務(wù)需要其他線程執(zhí)行,它只有回調(diào),即使有,也只是內(nèi)部嵌套的另一個宏任務(wù)。

簡單小結(jié)一下微任務(wù)和宏任務(wù)的本質(zhì)區(qū)別。

  • 宏任務(wù)特征:有明確的異步任務(wù)需要執(zhí)行和回調(diào);需要其他異步線程支持。

  • 微任務(wù)特征:沒有明確的異步任務(wù)需要執(zhí)行,只有回調(diào);不需要其他異步線程支持。

定時器誤差

事件循環(huán)中,總是先執(zhí)行同步代碼后,才會去任務(wù)隊列中取出異步回調(diào)來執(zhí)行。當(dāng)執(zhí)行 setTimeout 時,瀏覽器啟動新的線程去計時,計時結(jié)束后觸發(fā)定時器事件將回調(diào)存入宏任務(wù)隊列,等待 JS 主線程來取出執(zhí)行。如果這時主線程還在執(zhí)行同步任務(wù)的過程中,那么此時的宏任務(wù)就只有先掛起,這就造成了計時器不準(zhǔn)確的問題。同步代碼耗時越長,計時器的誤差就越大。不僅同步代碼,由于微任務(wù)會優(yōu)先執(zhí)行,所以微任務(wù)也會影響計時,假設(shè)同步代碼中有一個死循環(huán)或者微任務(wù)中遞歸不斷在啟動其他微任務(wù),那么宏任務(wù)里面的代碼可能永遠(yuǎn)得不到執(zhí)行。所以主線程代碼的執(zhí)行效率提升是一件很重要的事情。

JavaScript事件循環(huán)的原理是什么

一個很簡單的場景就是我們界面上有一個時鐘精確到秒,每秒更新一次時間。你會發(fā)現(xiàn)有時候秒數(shù)會直接跳過 2 秒間隔,就是這個原因。

視圖更新渲染

微任務(wù)隊列執(zhí)行完成后,也就是一次事件循環(huán)結(jié)束后,瀏覽器會執(zhí)行視圖渲染,當(dāng)然這里會有瀏覽器的優(yōu)化,可能會合并多次循環(huán)的結(jié)果做一次視圖重繪,因此視圖更新是在事件循環(huán)之后,所以并不是每一次操作 Dom 都一定會立馬刷新視圖。視圖重繪之前會先執(zhí)行 requestAnimationFrame 回調(diào),那么對于 requestAnimationFrame 是微任務(wù)還是宏任務(wù)是有爭議的,在這里看來,它應(yīng)該既不屬于微任務(wù),也不屬于宏任務(wù)。

NodeJS 中的事件循環(huán)

JS 引擎本身不實現(xiàn)事件循環(huán)機(jī)制,這是由它的宿主實現(xiàn)的,瀏覽器中的事件循環(huán)主要是由瀏覽器來實現(xiàn),而在 NodeJS 中也有自己的事件循環(huán)實現(xiàn)。NodeJS 中也是循環(huán) + 任務(wù)隊列的流程以及微任務(wù)優(yōu)先于宏任務(wù),大致表現(xiàn)和瀏覽器是一致的。不過它與瀏覽器中也有一些差異,并且新增了一些任務(wù)類型和任務(wù)階段。接下來我們介紹下 NodeJS 中的事件循環(huán)流程。

NodeJS 中的異步方法

因為都是基于 V8 引擎,瀏覽器中包含的異步方式在 NodeJS 中也是一樣的。另外 NodeJS 中還有一些其他常見異步形式。

  • 文件 I/O:異步加載本地文件。

  • setImmediate():與 setTimeout 設(shè)置 0ms  類似,在某些同步任務(wù)完成后立馬執(zhí)行。

  • process.nextTick():在某些同步任務(wù)完成后立馬執(zhí)行。

  • server.close、socket.on('close',...)等:關(guān)閉回調(diào)。

想象一下,如果上面的形式和 setTimeout、promise 等同時存在,如何分析出代碼的執(zhí)行順序呢?只要我們理解了 NodeJS 的事件循環(huán)機(jī)制,也就清楚了。

事件循環(huán)模型

NodeJS 的跨平臺能力和事件循環(huán)機(jī)制都是基于 Libuv 庫實現(xiàn)的,你不用關(guān)心這個庫的具體內(nèi)容。我們只需要知道 Libuv 庫是事件驅(qū)動的,并且封裝和統(tǒng)一了不同平臺的 API 實現(xiàn)。

NodeJS   中 V8 引擎將 JS 代碼解析后調(diào)用 Node API,然后 Node API 將任務(wù)交給 Libuv 去分配,最后再將執(zhí)行結(jié)果返回給 V8 引擎。在 Libux 中實現(xiàn)了一套事件循環(huán)流程來管理這些任務(wù)的執(zhí)行,所以 NodeJS 的事件循環(huán)主要是在 Libuv 中完成的。

JavaScript事件循環(huán)的原理是什么

下面我們來看看 Libuv 中的循環(huán)是怎樣的。

事件循環(huán)各階段

在 NodeJS   中 JS 的執(zhí)行,我們主要需要關(guān)心的過程分為以下幾個階段,下面每個階段都有自己單獨的任務(wù)隊列,當(dāng)執(zhí)行到對應(yīng)階段時,就判斷當(dāng)前階段的任務(wù)隊列是否有需要處理的任務(wù)。

  • timers 階段:執(zhí)行所有 setTimeout() 和 setInterval() 的回調(diào)。

  • pending callbacks 階段:某些系統(tǒng)操作的回調(diào),如  TCP  鏈接錯誤。除了 timers、close、setImmediate 的其他大部分回調(diào)在此階段執(zhí)行。

  • poll 階段:輪詢等待新的鏈接和請求等事件,執(zhí)行 I/O 回調(diào)等。V8 引擎將 JS 代碼解析并傳入 Libuv 引擎后首先進(jìn)入此階段。如果此階段任務(wù)隊列已經(jīng)執(zhí)行完了,則進(jìn)入 check 階段執(zhí)行 setImmediate 回調(diào)(如果有 setImmediate),或等待新的任務(wù)進(jìn)來(如果沒有 setImmediate)。在等待新的任務(wù)時,如果有 timers 計時到期,則會直接進(jìn)入 timers 階段。此階段可能會阻塞等待。

  • check 階段:setImmediate 回調(diào)函數(shù)執(zhí)行。

  • close callbacks 階段:關(guān)閉回調(diào)執(zhí)行,如 socket.on('close', ...)。

JavaScript事件循環(huán)的原理是什么

上面每個階段都會去執(zhí)行完當(dāng)前階段的任務(wù)隊列,然后繼續(xù)執(zhí)行當(dāng)前階段的微任務(wù)隊列,只有當(dāng)前階段所有微任務(wù)都執(zhí)行完了,才會進(jìn)入下個階段。這里也是與瀏覽器中邏輯差異較大的地方,不過瀏覽器不用區(qū)分這些階段,也少了很多異步操作類型,所以不用刻意去區(qū)分兩者區(qū)別。代碼如下所示:

const fs = require('fs');
fs.readFile(__filename, (data) => {    // poll(I/O 回調(diào)) 階段
    console.log('readFile')    Promise.resolve().then(() => {        console.error('promise1')
    })    Promise.resolve().then(() => {        console.error('promise2')
    })
});setTimeout(() => {    // timers 階段
    console.log('timeout');    Promise.resolve().then(() => {        console.error('promise3')
    })    Promise.resolve().then(() => {        console.error('promise4')
    })
}, 0);// 下面代碼只是為了同步阻塞1秒鐘,確保上面的異步任務(wù)已經(jīng)準(zhǔn)備好了var startTime = new Date().getTime();var endTime = startTime;while(endTime - startTime < 1000) {
    endTime = new Date().getTime();
}// 最終輸出 timeout promise3 promise4 readFile promise1 promise2

另一個與瀏覽器的差異還體現(xiàn)在同一個階段里的不同任務(wù)執(zhí)行,在 timers 階段里面的宏任務(wù)、微任務(wù)測試代碼如下所示:

setTimeout(() => {  console.log('timeout1')    Promise.resolve().then(function() {    console.log('promise1')
  })
}, 0);setTimeout(() => {  console.log('timeout2')    Promise.resolve().then(function() {    console.log('promise2')
  })
}, 0);

  • 瀏覽器中運行

    每次宏任務(wù)完成后都會優(yōu)先處理微任務(wù),輸出“timeout1”、“promise1”、“timeout2”、“promise2”。

  • NodeJS 中運行

    因為輸出 timeout1 時,當(dāng)前正處于  timers 階段,所以會先將所有 timer 回調(diào)執(zhí)行完之后再執(zhí)行微任務(wù)隊列,即輸出“timeout1”、“timeout2”、“promise1”、“promise2”。

上面的差異可以用瀏覽器和 NodeJS 10 對比驗證。是不是感覺有點反程序員?因此 NodeJS 在版本 11 之后,就修改了此處邏輯使其與瀏覽器盡量一致,也就是每個 timer 執(zhí)行后都先去檢查一下微任務(wù)隊列,所以 NodeJS 11 之后的輸出已經(jīng)和瀏覽器一致了。

nextTick、setImmediate 和 setTimeout

實際項目中我們常用 Promise 或者 setTimeout 來做一些需要延時的任務(wù),比如一些耗時計算或者日志上傳等,目的是不希望它的執(zhí)行占用主線程的時間或者需要依賴整個同步代碼執(zhí)行完成后的結(jié)果。

NodeJS 中的 process.nextTick() 和 setImmediate() 也有類似效果。其中 setImmediate() 我們前面已經(jīng)講了是在 check 階段執(zhí)行的,而 process.nextTick() 的執(zhí)行時機(jī)不太一樣,它比 promise.then() 的執(zhí)行還早,在同步任務(wù)之后,其他所有異步任務(wù)之前,會優(yōu)先執(zhí)行 nextTick??梢韵胂笫前?nextTick 的任務(wù)放到了當(dāng)前循環(huán)的后面,與 promise.then() 類似,但比 promise.then() 更前面。意思就是在當(dāng)前同步代碼執(zhí)行完成后,不管其他異步任務(wù),先盡快執(zhí)行 nextTick。如下面的代碼,因此這里的 nextTick 其實應(yīng)該更符合“setImmediate”這個命名才對。

setTimeout(() => {    console.log('timeout');
}, 0);Promise.resolve().then(() => {    console.error('promise')
})
process.nextTick(() => {    console.error('nextTick')
})// 輸出:nextTick、promise、timeout

接下來我們再來看看 setImmediate 和 setTimeout,它們是屬于不同的執(zhí)行階段了,分別是 timers 階段和 check 階段。

setTimeout(() => {  console.log('timeout');
}, 0);setImmediate(() => {  console.log('setImmediate');
});// 輸出:timeout、 setImmediate

分析上面代碼,第一輪循環(huán)后,分別將 setTimeout   和 setImmediate 加入了各自階段的任務(wù)隊列。第二輪循環(huán)首先進(jìn)入  timers 階段,執(zhí)行定時器隊列回調(diào),然后  pending callbacks 和 poll 階段沒有任務(wù),因此進(jìn)入check 階段執(zhí)行 setImmediate 回調(diào)。所以最后輸出為“timeout”、“setImmediate”。當(dāng)然這里還有種理論上的極端情況,就是第一輪循環(huán)結(jié)束后耗時很短,導(dǎo)致 setTimeout 的計時還沒結(jié)束,此時第二輪循環(huán)則會先執(zhí)行 setImmediate 回調(diào)。

再看這下面一段代碼,它只是把上一段代碼放在了一個 I/O 任務(wù)回調(diào)中,它的輸出將與上一段代碼相反。

const fs = require('fs');
fs.readFile(__filename, (data) => {    console.log('readFile');    setTimeout(() => {        console.log('timeout');
    }, 0);    setImmediate(() => {        console.log('setImmediate');
    });
});// 輸出:readFile、setImmediate、timeout

如上面代碼所示:

  • 第一輪循環(huán)沒有需要執(zhí)行的異步任務(wù)隊列;

  • 第二輪循環(huán) timers 等階段都沒有任務(wù),只有 poll 階段有 I/O 回調(diào)任務(wù),即輸出“readFile”;

  • 參考前面事件階段的說明,接下來,poll 階段會檢測如果有 setImmediate 的任務(wù)隊列則進(jìn)入 check 階段,否則再進(jìn)行判斷,如果有定時器任務(wù)回調(diào),則回到 timers 階段,所以應(yīng)該進(jìn)入 check 階段執(zhí)行 setImmediate,輸出“setImmediate”;

  • 然后進(jìn)入最后的 close callbacks 階段,本次循環(huán)結(jié)束;

  • 最后進(jìn)行第三輪循環(huán),進(jìn)入 timers 階段,輸出“timeout”。

所以最終輸出“setImmediate”在“timeout”之前。可見這兩者的執(zhí)行順序與當(dāng)前執(zhí)行的階段有關(guān)系。

以上就是“JavaScript事件循環(huán)的原理是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


當(dāng)前文章:JavaScript事件循環(huán)的原理是什么
網(wǎng)頁URL:http://weahome.cn/article/igsiee.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部