本篇文章給大家?guī)?lái)了我們的JavaScript單線程的相關(guān)知識(shí),JavaScript是一門單線程的語(yǔ)言,為什么JavaScript可以一邊執(zhí)行定時(shí)器一邊執(zhí)行函數(shù),希望對(duì)大家有幫助。
1. 進(jìn)程與線程
1.1 進(jìn)程(Process)
是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。 在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體。是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體。
我們這里將進(jìn)程比喻為工廠的車間,它代表CPU所能處理的單個(gè)任務(wù)。任一時(shí)刻,CPU總是運(yùn)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)。
1.2 線程(thread)
是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
這里把線程比喻一個(gè)車間的工人,即一個(gè)車間可以允許由多個(gè)工人協(xié)同完成一個(gè)任務(wù)。
2. 多線程的瀏覽器
瀏覽器內(nèi)核是多線程,在內(nèi)核控制下各線程相互配合以保持同步,一個(gè)瀏覽器通常由以下常駐線程組成:
-
GUI 渲染線程
-
JavaScript引擎線程
-
事件觸發(fā)線程
-
定時(shí)觸發(fā)器線程
-
異步http請(qǐng)求線程
我們看到了JS引擎線程,非常的熟悉,沒(méi)有錯(cuò),這里是我們執(zhí)行javascript腳本程序的地方。
而JS引擎是多線程的,單線程是指JS引擎執(zhí)行JS時(shí)只分了一個(gè)線程給他執(zhí)行,意思是JS引擎分配了一個(gè)線程給JavaScript執(zhí)行,也就是我們所說(shuō)的單線程。
2.1 這里再說(shuō)下JS的執(zhí)行機(jī)制
由于JavaScript是單線程(一個(gè)Tab頁(yè)內(nèi)中無(wú)論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JavaScript程序)。
所以我們需要依靠任務(wù)隊(duì)列來(lái)進(jìn)行JavaScript代碼的執(zhí)行。
JS引擎會(huì)一直等待著任務(wù)隊(duì)列中任務(wù)的到來(lái),然后執(zhí)行任務(wù)。
同步任務(wù)這么執(zhí)行當(dāng)然沒(méi)問(wèn)題,我們把任務(wù)都放在任務(wù)隊(duì)列里,一個(gè)一個(gè)執(zhí)行,邏輯很清晰。但是,如果我們向后臺(tái)發(fā)送請(qǐng)求,發(fā)送加接收這段時(shí)間可能需要一秒,我們不能等它一秒吧,如果請(qǐng)求五次,那就等五秒?顯示不符合我們的需求,所以,我們需要異步任務(wù)來(lái)處理這個(gè)問(wèn)題。
2.2 同步任務(wù)和異步任務(wù)
同步任務(wù)是指在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能繼續(xù)執(zhí)行下一個(gè)任務(wù),當(dāng)我們打開網(wǎng)站時(shí),網(wǎng)站的渲染過(guò)程,比如元素的渲染,其實(shí)就是一個(gè)同步任務(wù)
異步任務(wù)是指不進(jìn)入主線程,而進(jìn)入任務(wù)隊(duì)列的任務(wù),只有任務(wù)隊(duì)列通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程,當(dāng)我們打開網(wǎng)站時(shí),像圖片的加載,音樂(lè)的加載,其實(shí)就是一個(gè)異步任務(wù)。
大家肯定對(duì)Event Loop有比較具象的認(rèn)知,這邊我不詳細(xì)說(shuō)了,不懂可以和我說(shuō),我再講就是了。
3. 本文重重點(diǎn)–可直接看
但是,大家有沒(méi)有對(duì)任務(wù)隊(duì)列抱有疑問(wèn)?這是個(gè)對(duì)象?是個(gè)數(shù)組?按我的邏輯來(lái)說(shuō),我們JavaScript主線程執(zhí)行同步函數(shù),異步函數(shù)可以放在任務(wù)隊(duì)列里,這個(gè)任務(wù)隊(duì)列可以是個(gè)對(duì)象,當(dāng)我們執(zhí)行完同步任務(wù)的時(shí)候,把這個(gè)對(duì)象(任務(wù)隊(duì)列)壓進(jìn)主線程中就可以了,但是事實(shí)并不我想的這樣的。
Evnet Loop的任務(wù)隊(duì)列放在了瀏覽器的事件觸發(fā)線程中,當(dāng)JS引擎執(zhí)行異步函數(shù)的時(shí)候,會(huì)將異步任務(wù)放在事件觸發(fā)線程中,當(dāng)對(duì)應(yīng)的異步任務(wù)符合觸發(fā)條件被觸發(fā)時(shí),事件觸發(fā)線程會(huì)把異步任務(wù)添加到JS引擎中的主線程的隊(duì)尾,等待執(zhí)行。
是不是和我們想象的JavaScript單線程不太一樣?好吧,確實(shí)不太一樣,所以最后的結(jié)論是,我們所說(shuō)的任務(wù)隊(duì)列竟然是一個(gè)線程。
然后,說(shuō)回我們開頭剛開始說(shuō)過(guò)的定時(shí)器,大家基本也能猜出來(lái)了,它是由定時(shí)器線程控制的。
因?yàn)镴avaScript是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確,因此很有必要單獨(dú)開一個(gè)線程用來(lái)計(jì)時(shí)。
當(dāng)使用setTimeout或setInterval時(shí),它需要定時(shí)器線程計(jì)時(shí),計(jì)時(shí)完成后就會(huì)將特定的事件推入事件隊(duì)列中。
4. 結(jié)論
所以說(shuō),我們說(shuō)JavaScript是單線程的沒(méi)錯(cuò),就是天王老子來(lái)了它也是單線程的,但是我們的Event Loop和定時(shí)器是放在其他線程中的。
5. V8引擎–擴(kuò)展
V8引擎是一個(gè)JavaScript引擎實(shí)現(xiàn),最初由一些語(yǔ)言方面專家設(shè)計(jì),后被谷歌收購(gòu),隨后谷歌對(duì)其進(jìn)行了開源。
V8使用C++開發(fā),在運(yùn)行JavaScript之前,相比其它的JavaScript的引擎轉(zhuǎn)換成字節(jié)碼或解釋執(zhí)行,V8將其編譯成原生機(jī)器碼(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如內(nèi)聯(lián)緩存(inline caching)等方法來(lái)提高性能。
有了這些功能,JavaScript程序在V8引擎下的運(yùn)行速度媲美二進(jìn)制程序。V8支持眾多操作系統(tǒng),如windows、linux、android等,也支持其他硬件架構(gòu),如IA32,X64,ARM等,具有很好的可移植和跨平臺(tái)特性。
5.1 工作流程
V8引擎在執(zhí)行JavaScript的過(guò)程中,主要有兩個(gè)階段:編譯和運(yùn)行,與C++的執(zhí)行前完全編譯不同的是,JavaScript需要在用戶使用時(shí)完成編譯和執(zhí)行。在V8中,JavaScript相關(guān)代碼并非一下完成編譯的,而是在某些代碼需要執(zhí)行時(shí),才會(huì)進(jìn)行編譯,這就提高了響應(yīng)時(shí)間,減少了時(shí)間開銷。在V8引擎中,源代碼先被解析器轉(zhuǎn)變?yōu)槌橄笳Z(yǔ)法樹(AST),然后使用JIT編譯器的全代碼生成器從AST直接生成本地可執(zhí)行代碼。這個(gè)過(guò)程不同于JAVA先生成字節(jié)碼或中間表示,減少了AST到字節(jié)碼的轉(zhuǎn)換時(shí)間,提高了代碼的執(zhí)行速度。但由于缺少了轉(zhuǎn)換為字節(jié)碼這一中間過(guò)程,也就減少了優(yōu)化代碼的機(jī)會(huì)。
V8引擎編譯本地代碼時(shí)使用的主要類如下所示:
-
Script:表示JavaScript代碼,即包含源代碼,又包含編譯之后生成的本地代碼,即是編譯入口,又是運(yùn)行入口;
-
Compiler:編譯器類,輔組Script類來(lái)編譯生成代碼,調(diào)用解釋器(Parser)來(lái)生成AST和全代碼生成器,將AST轉(zhuǎn)變?yōu)楸镜卮a;
-
AstNode:抽象語(yǔ)法樹節(jié)點(diǎn)類,是其他所有節(jié)點(diǎn)的基類,包含非常多的子類,后面會(huì)針對(duì)不同的子類生成不同的本地代碼;
-
AstVisitor:抽象語(yǔ)法樹的訪問(wèn)者類,主要用來(lái)遍歷異構(gòu)的抽象語(yǔ)法樹;
-
FullCodeGenerator:AstVisitor類的子類,通過(guò)遍歷AST來(lái)為JavaScript生成本地可執(zhí)行代碼。
JavaScript代碼編譯的過(guò)程大致為:Script類調(diào)用Compiler類的Compile函數(shù)為其生成本地代碼。Compile函數(shù)先使用Parser類生成AST,再使用FullCodeGenerator類來(lái)生成本地代碼。本地代碼與具體的硬件平臺(tái)密切相關(guān),F(xiàn)ullCodeGenerator使用多個(gè)后端來(lái)生成與平臺(tái)相匹配的本地匯編代碼。由于FullCodeGenerator通過(guò)遍歷AST來(lái)為每個(gè)節(jié)點(diǎn)生成相應(yīng)的匯編代碼,缺失了全局視圖,節(jié)點(diǎn)之間的優(yōu)化也就無(wú)從談起
【