陸柳杰
摘要:該文主要是通過對瀏覽器中用戶操作、事件循環(huán)機(jī)制、任務(wù)隊列的分析來研究瀏覽器環(huán)境中JavaScript的內(nèi)部執(zhí)行機(jī)制,包括請求的發(fā)送,頁面的渲染過程,函數(shù)的執(zhí)行過程等。最終得出同步任務(wù)先于異步任務(wù)執(zhí)行,微任務(wù)先于宏任務(wù)執(zhí)行的結(jié)論。
關(guān)鍵詞:同步;異步;事件循環(huán);任務(wù)隊列;Promise
中圖分類號:TP391 ? ? ?文獻(xiàn)標(biāo)識碼:A
文章編號:1009-3044(2020)31-0066-03
1 引言
JavaScript(JS)是動態(tài)編程語言。利用HTML/CSS/JS,可以做出動態(tài)交互的網(wǎng)站。由布蘭登·艾克(Brendan Eich,Mozilla基金會、Mozilla項目和Mozilla公司的聯(lián)合創(chuàng)始人)發(fā)明[1]。雖然JavaScript的語法非常簡潔靈活,但是JavaScript的應(yīng)用場合極其廣泛,可以做PPT、照片庫、浮動布局和響應(yīng)按鈕點擊,復(fù)雜到游戲、2D/3D 動畫、大型數(shù)據(jù)庫驅(qū)動程序等等。開發(fā)者們基于JavaScript核心編寫了大量開源的實用工具,提高了開發(fā)效率。其中包括:
1)瀏覽器內(nèi)置的API提供了豐富的功能,比如:動態(tài)創(chuàng)建HTML和設(shè)置CSS樣式、生成3D圖像與音頻樣本、從用戶的攝像頭采集處理視頻流等等。
2)開發(fā)人員在開發(fā)站點的時候可以利用已有的一些功能優(yōu)化自己的站點。
3)需要快速創(chuàng)建網(wǎng)站的時候可以利用一些現(xiàn)有開源的框架和庫。
JavaScript的一大特點就是單線程,單線程是指同一時間段只能處理一件任務(wù),其他的任務(wù)都要排隊[2]。JavaScript的核心功能是方便用戶通過點擊等DOM操作與機(jī)器交互。
瀏覽器要處理頁面渲染,交互事件,還有各種用戶輸入,任務(wù)很多,但是這么多任務(wù)又只能在一個主線程中執(zhí)行,必須有條不紊的執(zhí)行才不會發(fā)生阻塞。所以就需要一個機(jī)制來幫助瀏覽器執(zhí)行這一系列任務(wù)。這就是任務(wù)調(diào)度系統(tǒng),也就是我們接下來要講的Eventloop。
2 瀏覽器基本工作原理
2.1 瀏覽器基本介紹
瀏覽器對JS的解析執(zhí)行是單線程的,并且是按一定的順序執(zhí)行的。然而在Javascript中又有異步的概念,但是這兩者并不沖突。因為除了解析JS代碼的引擎,瀏覽器中還有其他很多的引擎,解析JS的引擎被叫作主線程。還有一些事用戶界面渲染引擎,瀏覽器內(nèi)核引擎,呈現(xiàn)引擎,網(wǎng)絡(luò)引擎,JavaScript解釋器。
用戶在網(wǎng)頁上能看到的東西都屬于用戶界面,除了瀏覽器中控制臺的部分。瀏覽器引擎(瀏覽器內(nèi)核)是用戶界面和呈現(xiàn)引擎之間的紐帶,主要負(fù)責(zé)一些指令的傳送。呈現(xiàn)引擎負(fù)責(zé)顯示代碼,如果代碼是HTML格式的,呈現(xiàn)引擎就會解釋代碼的HTML標(biāo)簽和CSS樣式,最終將一個靜態(tài)頁面顯示在瀏覽器中。通常有網(wǎng)絡(luò)請求的地方就會用到瀏覽器中的網(wǎng)絡(luò)引擎,比如當(dāng)用戶發(fā)送HTTP或者HTTPS請求的時候。用戶界面上有按鈕,表單,鏈接,圖片,文字等各種資源。瀏覽器會提供一些通用的接口,供用戶調(diào)用。用于解析和執(zhí)行JavaScript代碼是JavaScript解釋器。最后一點不會經(jīng)常用到,但還是有必要說明,數(shù)據(jù)存儲屬于瀏覽器中的持久層。由于瀏覽器會在硬盤上存儲一些緩存,所以需要數(shù)據(jù)存儲這一層。新出的HTML5規(guī)范定義了一個瀏覽器內(nèi)部數(shù)據(jù)庫,方便存儲一些用戶臨時數(shù)據(jù)。使用起來很輕便。
2.2 瀏覽器引擎
瀏覽器引擎是多線程的,基本包含頁面渲染線程,JavaScript引擎線程,定時觸發(fā)器線程,事件觸發(fā)線程,異步http線程等。頁面渲染線程,主要是用來渲染html文件。JavaScript引擎線程是可以處理頁面節(jié)點的渲染。定時觸發(fā)器線程主要是用于某些需要定時觸發(fā)的任務(wù)。當(dāng)我們需要執(zhí)行異步操作,比如發(fā)送AJAX請求,并且需要數(shù)據(jù)實時回填的時候,就會用到瀏覽器的事件觸發(fā)線程。同時AJAX請求發(fā)送時瀏覽器會開一個新的線程,只要檢測到服務(wù)端有數(shù)據(jù)返回,就開始執(zhí)行異步回調(diào)里面的回調(diào)函數(shù),暫時不能執(zhí)行的就需要放到隊列中等待后續(xù)通知執(zhí)行。
JavaScript引擎和可視化引擎是不能同時運行的,不能在操作dom的同時渲染頁面。那么執(zhí)行順序必然是有先后,所以Javascript引擎會有一個事件監(jiān)測的功能,會不停地取的檢查js引擎的主線程是否已經(jīng)執(zhí)行完,并且將執(zhí)行棧清空。只要當(dāng)前執(zhí)行棧被清空了,就會通知主線程執(zhí)行下一個任務(wù)。
2.3 Javascript引擎執(zhí)行機(jī)制
由于js的運行環(huán)境是單線程的,所以執(zhí)行其他的異步操作必須利用瀏覽器來的其他線程來實現(xiàn)。其中最主要的是運用了瀏覽器的事件觸發(fā)線程和js引擎線程,開啟相關(guān)的網(wǎng)絡(luò)服務(wù)和定時器也會用到其他的線程。具體流程如圖1所示。當(dāng)一段任務(wù)開始執(zhí)行的時候,Javascript引擎先判斷這段代碼是否屬于異步代碼,如果是同步代碼,就先按順序執(zhí)行同步代碼,如果是異步代碼就把它放入任務(wù)隊列中,等待同步代碼執(zhí)行完,再從任務(wù)隊列中取出執(zhí)行。
3 任務(wù)隊列運行流程
3.1 單線程
Javascript在瀏覽器中的運行是單線程,當(dāng)前任務(wù)沒有執(zhí)行完必然會阻塞下一個任務(wù)的執(zhí)行。然而瀏覽器是由事件驅(qū)動的,瀏覽器中存在很多異步行為。Javascript單線程引擎是解析它的任務(wù)隊列的,比如瀏覽器點擊事件,Ajax請求,回調(diào)函數(shù)都是異步的,所以會被放入到執(zhí)行隊列中。
3.1.1 ?同步異步
同步任務(wù)在主線程上執(zhí)行的時候不能插入其他同步任務(wù),必須等當(dāng)前任務(wù)執(zhí)行完才能執(zhí)行下一個任務(wù),如果AJAX請求是同步的,服務(wù)器數(shù)據(jù)一直不返回就會引起阻塞。所以這個時候就需要異步任務(wù)。
異步任務(wù)在同步任務(wù)沒有執(zhí)行完的時候,是不會進(jìn)入主線程的,會暫時進(jìn)入任務(wù)隊列排隊,只有當(dāng)主線程中的同步任務(wù)都執(zhí)行完,主線程才會通知任務(wù)隊列,異步任務(wù)可以執(zhí)行了,并且從隊列中取出位于第一的任務(wù)進(jìn)入主線程中執(zhí)行。