摘要:瀏覽器推遲事件直到所有的腳本都處于狀態(tài)。解析器將處理執(zhí)行這個(gè)腳本。創(chuàng)建這個(gè)腳本的解析器的文檔有正在阻塞腳本執(zhí)行腳本元素為等待解析阻塞的腳本的狀態(tài),同一時(shí)刻只能有一個(gè)這樣的腳本存在。解析器將一個(gè)或多個(gè)字符轉(zhuǎn)換為表并處理,這個(gè)過(guò)程是一個(gè)典型的。
前言
本文主要對(duì)W3C規(guī)范中關(guān)于script標(biāo)簽和event loop相關(guān)的篇幅做了簡(jiǎn)單的探討,針對(duì)一些必要的相關(guān)概念進(jìn)行了適當(dāng)?shù)臉?biāo)注和說(shuō)明。雖然之前接觸過(guò),但都過(guò)于零散,希望借此機(jī)會(huì),能夠?qū)@些概念能夠一個(gè)稍微全面一點(diǎn)的認(rèn)識(shí),也希望和大家進(jìn)行交流。由于知識(shí)的深度和廣度以及英語(yǔ)水平的不足,如有錯(cuò)誤,還望包涵指正。
小波折雖然之前查過(guò)W3C和WHATWG的關(guān)系,但是翻譯得差不多的時(shí)候有個(gè)問(wèn)題去WHATWG提了issue,才被domenic大大告知我可能看了"假規(guī)范"- -(具體可參考鏈接1,鏈接2,F(xiàn)ork tracking),最新的規(guī)范在這,大部分還是基本一致的,新增了一些比如type=module的內(nèi)容等等,還有排版呀,有的描述等等有一些變化,有興趣的可以去看看。
HTML解析瀏覽器HTML解析過(guò)程如下:
The exact processing details for these attributes are, for mostly historical reasons, somewhat non-trivial, involving a number of aspects of HTML.The implementation requirements are therefore by necessity scattered throughout the specification.
可以看到,規(guī)范也提到了規(guī)范只是一個(gè)參考,具體實(shí)現(xiàn)因人而異。在測(cè)試中,我列出以下發(fā)現(xiàn)和期待討論的話題,希望對(duì)自己和他人都能起到幫助:
Script標(biāo)簽關(guān)于script標(biāo)簽基本信息的一些描述這里不再過(guò)多介紹,自己有幾個(gè)比較關(guān)心的點(diǎn)在以下列出。
defer,async屬性對(duì)于普通腳本,defer腳本,async腳本,有如下總結(jié):
1.對(duì)于普通的腳本,有兩點(diǎn)需要注意。
第一:并不是在fetch的時(shí)候完全“阻止”后續(xù)標(biāo)簽的解析。我們從timeline可以看到,在第一次praseHTML的最開(kāi)始,就已經(jīng)將頁(yè)面所需的所有靜態(tài)資源請(qǐng)求send出去了(具體可參考瀏覽器預(yù)解析加載機(jī)制)。所以腳本是無(wú)法“阻止”后續(xù)標(biāo)簽中引用外部資源的請(qǐng)求不被發(fā)送的。并且在解析這個(gè)腳本到finish load這段時(shí)間,還有很多其他操作,如擴(kuò)展程序的腳本執(zhí)行,某些VM語(yǔ)句執(zhí)行,install這一腳本之前的腳本中的定時(shí)器等等。
第二:fetch之后接收完所有數(shù)據(jù)包最后完成finish load之后,并不是立即執(zhí)行這個(gè)腳本內(nèi)的內(nèi)容。而是先要判斷這個(gè)腳本在所有腳本中的順序,必須確定這個(gè)腳本之前的所有普通腳本執(zhí)行完畢后,才會(huì)執(zhí)行這個(gè)腳本。
script標(biāo)簽處理模型處理模型擁有以下7種狀態(tài)屬性:
"already started" ->> "parser-inserted" ->> "non-blocking" ->> "ready to be parser-executed" ->> "the script’s type" ->> "is from an external file" ->> "the script’s script"
最后一步也就是異步將預(yù)處理腳本(見(jiàn)下)的結(jié)果設(shè)置為腳本的script屬性的值,無(wú)論這個(gè)值是正確的還是錯(cuò)誤的,都應(yīng)標(biāo)記腳本為ready狀態(tài),這意味著這之后可以觸發(fā)其他行為。瀏覽器推遲load事件直到所有的腳本都處于ready狀態(tài)。
關(guān)于這些狀態(tài)的描述內(nèi)容并不多,比如初始時(shí)還沒(méi)有"already started",HTML解析器解析到之后立即置為"already started",初始時(shí)沒(méi)有"parser-inserted",當(dāng)HTML解析器把節(jié)點(diǎn)插入到父節(jié)點(diǎn)的時(shí)候,置為"parser-inserted",當(dāng)HTML解析器在創(chuàng)建節(jié)點(diǎn)對(duì)象的時(shí)候默認(rèn)是"non-blocking",當(dāng)HTML解析器把節(jié)點(diǎn)插入到父節(jié)點(diǎn)的時(shí)候,置為"blocking"(實(shí)際是設(shè)置為false,便于理解故作此翻譯,不要打我。。),如果腳本有async屬性,那么又置為"non-blocking"避免阻塞解析,等等等等。
具體要了解的話建議查閱文檔。這里我們探討下最關(guān)鍵的部分-預(yù)處理腳本(原文為prepare a script,感覺(jué)翻譯成準(zhǔn)備,預(yù)備都不太合適,故作此翻譯,如果有更好的翻譯還希望指正),"the script"s type","from an external file","the script"s script"都是在這一階段確定的:
預(yù)處理腳本當(dāng)一個(gè)未被標(biāo)記為"parser-inserted"的腳本元素經(jīng)歷以下3個(gè)事件中的任意一個(gè)時(shí),瀏覽器必須立即預(yù)處理這個(gè)腳本元素:
1.在dom節(jié)點(diǎn)中順序先于(先于是指按照前序進(jìn)行深度優(yōu)先遍歷的時(shí)候)這個(gè)腳本的腳本被插入到dom樹(shù)之后,這個(gè)腳本元素被插入到文檔中。
2.在所有腳本元素都被插入完畢后,這個(gè)腳本元素在文檔中且有其他節(jié)點(diǎn)被插入到這個(gè)腳本元素中。
3.腳本元素已經(jīng)在文檔中并且之前沒(méi)有src屬性,但是現(xiàn)在被設(shè)置了src屬性。
為了預(yù)處理一個(gè)腳本,瀏覽器必須進(jìn)行以下步驟:
前面1-18步主要考慮的是不需要執(zhí)行或者說(shuō)不符合執(zhí)行條件的時(shí)候就中斷預(yù)處理過(guò)程從而不執(zhí)行這個(gè)腳本。比如發(fā)現(xiàn)還沒(méi)有"already started",比如沒(méi)有src屬性且腳本的內(nèi)容為空或者只有注釋?zhuān)_本元素沒(méi)有在文檔中,type和language屬性不符合規(guī)范,用戶禁用了JS等等。除了這些之外,還有些諸如腳本有charset則設(shè)置,沒(méi)有就用文檔本身的charset。還有有些只是規(guī)范有提及,但是沒(méi)有瀏覽器或者不是所有瀏覽器都實(shí)現(xiàn)了,比如for,event,nonce屬性等等。另外還有一些其他的考慮,這里就不一一贅述了,詳細(xì)的可以參考規(guī)范。
下面著重來(lái)看一下19-20步:
第19步:如果腳本元素沒(méi)有src屬性,則進(jìn)行下列的步驟:
Let source text等于yourScriptElement.text的值。
將腳本的type屬性設(shè)置為"classic"
Let script作為用source text和settings創(chuàng)建的腳本的結(jié)果。
設(shè)置the script’s script為上一步的script。
讓腳本處于ready狀態(tài)
第20步:然后,選擇符合下列情形的第一個(gè)進(jìn)行執(zhí)行:
Type 1:
the script’s type | 是否有src屬性 | 是否有defer屬性 | 是否有async屬性 | 其他條件 |
---|---|---|---|---|
"classic" | 是 | 是 | 否 | 元素已經(jīng)"parser-inserted" |
將腳本元素添加到將要執(zhí)行的腳本的集合的末尾。
當(dāng)腳本處于ready狀態(tài)的時(shí)候,設(shè)置腳本元素的"ready to be parser-executed"標(biāo)記。解析器將處理執(zhí)行這個(gè)腳本。
Type 2:the script’s type | 是否有src屬性 | 是否有defer屬性 | 是否有async屬性 | 其他條件 |
---|---|---|---|---|
"classic" | 是 | 否 | 否 | 元素已經(jīng)"parser-inserted" |
腳本元素為"等待解析阻塞的腳本"(見(jiàn)步驟末尾)的狀態(tài),同一時(shí)刻只能有一個(gè)這樣的腳本存在。當(dāng)腳本處于ready狀態(tài)的時(shí)候,設(shè)置腳本元素的"ready to be parser-executed"標(biāo)記。解析器將處理執(zhí)行這個(gè)腳本。
Type 3:the script’s type | 是否有src屬性 | 是否有defer屬性 | 是否有async屬性 | 其他條件 |
---|---|---|---|---|
"classic" | 是 | 是或者否(意思為是或者否都是一樣的) | 否 | 元素上沒(méi)有"non-blocking"標(biāo)記 |
盡快當(dāng)預(yù)處理腳本一開(kāi)始的時(shí)候按順序?qū)⒛_本元素添加到將要執(zhí)行的腳本的集合的末尾。
當(dāng)腳本為ready狀態(tài)的時(shí)候,進(jìn)行下面的步驟:
1.如果這個(gè)腳本現(xiàn)在不是將要執(zhí)行的腳本的集合的第一個(gè)元素,則標(biāo)記腳本為ready,但是中斷剩余的步驟,不執(zhí)行這個(gè)腳本。
2.執(zhí)行腳本。
3.移除將要執(zhí)行的腳本的集合中的第一個(gè)元素。
4.如果將要執(zhí)行的腳本的集合仍然不為空且第一個(gè)元素被標(biāo)記為ready,那么跳回到第2步。
Type 4:the script’s type | 是否有src屬性 | 是否有defer屬性 | 是否有async屬性 | 其他條件 |
---|---|---|---|---|
"classic" | 是 | 是或者否 | 是或者否 | 不適用 |
盡快當(dāng)預(yù)處理腳本一開(kāi)始的時(shí)候?qū)⒛_本元素添加到將要執(zhí)行的腳本的集合的末尾。
當(dāng)腳本為ready狀態(tài)的時(shí)候,執(zhí)行腳本并將它從集合中移除。
Type 5:the script’s type | 是否有src屬性 | 是否有defer屬性 | 是否有async屬性 | 其他條件 |
---|---|---|---|---|
"classic" | 否 | 是或者否 | 是或者否 | 元素已經(jīng)"parser-inserted",XML或者HTML解析器的script-nesting-level比創(chuàng)建這個(gè)腳本的低或者相等。創(chuàng)建這個(gè)腳本的解析器的文檔有css正在阻塞腳本執(zhí)行 |
腳本元素為"等待解析阻塞的腳本"的狀態(tài),同一時(shí)刻只能有一個(gè)這樣的腳本存在。設(shè)置腳本元素的"ready to be parser-executed"標(biāo)記。解析器將處理執(zhí)行這個(gè)腳本。
Type 6(其他情形):立即執(zhí)行這個(gè)腳本,即使有其他腳本正在執(zhí)行。
總共就這6種情形,下面有一個(gè)上面提到的概念的補(bǔ)充說(shuō)明
等待解析阻塞的腳本:
如果一個(gè)阻塞了解析的腳本元素在它停止阻塞解析前被移動(dòng)到了另一個(gè)document中,盡管如此,它仍然會(huì)阻塞解析直到造成它阻塞的原因消除。(例如,如果這個(gè)腳本元素由于有一個(gè)css阻塞了它而變成一個(gè)等待解析阻塞的腳本,但是然后這個(gè)腳本在css加載完畢前被移動(dòng)到了另一個(gè)文檔中,這個(gè)腳本仍然會(huì)阻塞解析直到css加載完畢(但是阻塞的是另外那個(gè)文檔的解析了),但在這段期間,原來(lái)文檔的腳本執(zhí)行和HTML解析是暢通的)
在規(guī)范中user agents指的是實(shí)現(xiàn)了這些規(guī)范的應(yīng)用。為了更好的敘述,以下我們暫且用瀏覽器來(lái)代替這一描述。
為了協(xié)調(diào)事件,用戶接口,腳本,渲染,網(wǎng)絡(luò)等等,瀏覽器必須使用event loops。對(duì)于event loops,它有兩種類(lèi)型,一種是針對(duì)瀏覽器上下文(請(qǐng)務(wù)必先了解這一概念)而言,另一種是針對(duì)Wokrer而言。由于對(duì)Worker不太熟悉,我們這里也主要探討瀏覽器相關(guān)的東西,所以以下都不再敘述Worker相關(guān)內(nèi)容。
一個(gè)event loop有一個(gè)或者多個(gè)任務(wù)隊(duì)列。一個(gè)任何隊(duì)列是一系列有序的任務(wù)集合,這樣的隊(duì)列是通過(guò)下面這些算法來(lái)工作的:
Events:通常對(duì)于專(zhuān)用任務(wù)而言,dispatch一個(gè)Event對(duì)象給一個(gè)特定的EventTarget對(duì)象。另外,并不是所有的事件都是通過(guò)任務(wù)隊(duì)列來(lái)dispatch(哪些不是呢,可參考區(qū)別)。
Parsing:HTML解析器將一個(gè)或多個(gè)字符轉(zhuǎn)換為token表并處理,這個(gè)過(guò)程是一個(gè)典型的task。
Callbacks:調(diào)用一個(gè)回調(diào)函數(shù)常,常適用于專(zhuān)有任務(wù)。
Using a resource:當(dāng)fetch一個(gè)資源的時(shí)候,如果fetch發(fā)生在一個(gè)非阻塞的方法,一旦資源的部分或者全部是可用的,也會(huì)被當(dāng)作一個(gè)任務(wù)執(zhí)行(即timeline中的receive data和finish loading)。
Reacting to DOM manipulation:為了響應(yīng)dom變化也會(huì)導(dǎo)致一些元素產(chǎn)生task。如當(dāng)一個(gè)元素被插入到文檔中的時(shí)候。(意思就是說(shuō)插入之后會(huì)導(dǎo)致瀏覽器重新計(jì)算布局,渲染,一些監(jiān)聽(tīng)節(jié)點(diǎn)變化的事件也會(huì)被觸發(fā),這些都是task)
每一個(gè)在瀏覽器上下文的event loop中的task都與Document對(duì)象(準(zhǔn)確的說(shuō)是實(shí)現(xiàn)了Document接口的對(duì)象,規(guī)范也提及過(guò)為了便于敘述不采用這種準(zhǔn)確的說(shuō)法,因?yàn)樘L(zhǎng))相關(guān)聯(lián)。如果某個(gè)task被加入了某個(gè)元素的context的隊(duì)列,那么這個(gè)document對(duì)象就是這個(gè)元素的node document。如果某個(gè)task被加入了某個(gè)瀏覽器上下文的context的隊(duì)列,那么在入隊(duì)列的時(shí)候,這個(gè)document對(duì)象就是瀏覽器上下文的active document。如果某個(gè)task是通過(guò)腳本或者是針對(duì)腳本的,那么這個(gè)document對(duì)象就是通過(guò)腳本的配置對(duì)象指定的responsible document(現(xiàn)在想想responsible這個(gè)詞在這里還是挺有意思,因?yàn)榧冹o態(tài)頁(yè)面的document是不需要對(duì)任何東西負(fù)責(zé)的)。
當(dāng)瀏覽器將一個(gè)task加入隊(duì)列的時(shí)候,它必須將這個(gè)task加入相關(guān)的event loop中的某一個(gè)任務(wù)隊(duì)列。
每一個(gè)task在定義時(shí)都會(huì)有指定的task source(一共有4種,DOM manipulation task source,user interaction task source,networking task source,history traversal task source)。所有來(lái)自一個(gè)特定的task source的task都必須被添加到一個(gè)特定的相同的event loop(例如Document對(duì)象產(chǎn)生的回調(diào)函數(shù),觸發(fā)在Document對(duì)象上的mouseover事件,Document中等待解析的任務(wù)等等,他們都有相同的事件源-Document),但是不同來(lái)自不同task source的task也許會(huì)被添加到不同的任務(wù)隊(duì)列。
例如,瀏覽器也許有一個(gè)針對(duì)鼠標(biāo)和鍵盤(pán)的任務(wù)隊(duì)列(它們都來(lái)自u(píng)ser interaction這一task source)和其他的任務(wù)隊(duì)列。那么相對(duì)其他任務(wù)隊(duì)列而言,瀏覽器也許會(huì)給鼠標(biāo)和鍵盤(pán)事件更高的優(yōu)先級(jí),來(lái)保持響應(yīng)與用戶的交互,但是這又不會(huì)饑餓其他任務(wù)隊(duì)列。并且絕不會(huì)將來(lái)自同一task source的事件顛倒次序執(zhí)行(意思就是task必須按照它添加時(shí)的順序去執(zhí)行)。
每一個(gè)event loop都有一個(gè)當(dāng)前執(zhí)行任務(wù)。初始時(shí)為null。它被用作處理reentrancy(可重入性,類(lèi)似于generator,在內(nèi)聯(lián)腳本中直接使用document.write就是這樣,因?yàn)檫@樣是把write的參數(shù)寫(xiě)到之前的input stream(就是還未解析的字節(jié)流)里面)。每一個(gè)event loop也有一個(gè)performing a microtask checkpoint 的flag,初始時(shí)為false。它被用作阻止對(duì)perform a microtask checkpoint這個(gè)算法的可重入性調(diào)用。
關(guān)于microtask:每一個(gè)event loop都有一個(gè)microtask隊(duì)列,處于microtask隊(duì)列而不是普通的task隊(duì)列中的task就叫做microtask。這里有兩種類(lèi)型的microtask,一種是單一回調(diào)函數(shù)microtask,一種是復(fù)合microtask。注意,規(guī)范中只針對(duì)單一回調(diào)函數(shù)microtask有具體描述。
一個(gè)event loop在它存在的期間必須不斷重復(fù)以下步驟:
1.取出某一個(gè)任務(wù)隊(duì)列隊(duì)列頭的任務(wù)(如果存在的話)。如果與瀏覽器上下文的event loop相關(guān)聯(lián)的Documents對(duì)象不是fully active狀態(tài),那么忽略這個(gè)task。瀏覽器也許會(huì)選擇任何一個(gè)任務(wù)隊(duì)列。如果沒(méi)有task可以取的話,跳到第6步。
2.將event loop的當(dāng)前運(yùn)行任務(wù)設(shè)置為上一步選擇到的task。
3.運(yùn)行這個(gè)task。
4.將event loop的當(dāng)前運(yùn)行任務(wù)設(shè)置為null。
5.將第3步中運(yùn)行的task從它的任務(wù)隊(duì)列中移除。(這也說(shuō)明之前取任務(wù)時(shí)進(jìn)行的隊(duì)列操作是peek,而不是poll)
6.執(zhí)行一個(gè)microtask checkpoint操作。因?yàn)橛悬c(diǎn)多,避免混亂我寫(xiě)在這7個(gè)步驟完畢后的位置。
7.更新渲染:如果這個(gè)event loop是瀏覽器上下文的event loop而非Worker的event loop,那么執(zhí)行如下步驟:
Let now等于now()方法的返回值。(可以理解為timeline中的start time)
Let docs等于與這個(gè)event loop相關(guān)聯(lián)的Document對(duì)象集合。這個(gè)集合是隨意排序的,但是要遵循一定的原則,具體可以參照規(guī)范。簡(jiǎn)單舉例來(lái)說(shuō),A這個(gè)Document嵌套了B和C,B嵌套了D。那么順序即可以是A,B,C,D也可以是A,B,D,C。只要保證C在B后面,B,C在A后面,D在B后面就行。
迭代docs,對(duì)于其中的每個(gè)doc。如果這里存在一個(gè)頂級(jí)的瀏覽器上下文B(頂級(jí)就是指嵌套瀏覽器上下文情況下最祖先的那個(gè)瀏覽器上下文,形象一點(diǎn)的描述可參考鏈接)且不會(huì)從這次更新渲染中受益,那么將docs中所有瀏覽器上下文的頂級(jí)瀏覽器上下文為B的Document對(duì)象移除。
一個(gè)頂級(jí)瀏覽器上下文是否會(huì)從渲染更新中受益取決與幾個(gè)方面,如更新頻率。舉例來(lái)說(shuō),如果瀏覽器嘗試60HZ的刷新頻率,那么這些步驟只有在每16.7ms內(nèi)才是有意義的。如果瀏覽器發(fā)現(xiàn)一個(gè)頂級(jí)瀏覽器上下文無(wú)法維持這個(gè)頻率,它也許會(huì)將docs集合中的所有document對(duì)應(yīng)的刷新頻率下調(diào)到30HZ,而不是偶爾下調(diào)頻率。(規(guī)范并不強(qiáng)制規(guī)定任何特定的模型用于何時(shí)更新渲染),類(lèi)似的,如果一個(gè)頂級(jí)瀏覽器上下文是在background中(不太明白,猜測(cè)是dispaly:none之類(lèi)的意思),那么瀏覽器也許會(huì)下調(diào)到4HZ,甚至更低。
另一個(gè)關(guān)于瀏覽器可能會(huì)跳過(guò)更新渲染的例子是確保某些task在某些task之后被立即執(zhí)行,這伴隨著僅僅是microtask checkpoints的交替。(或者沒(méi)有這些交替,例如requestAnimationFrame中animation幀的回調(diào)函數(shù)交替)。例如,瀏覽器也許希望合并定時(shí)器回調(diào)函數(shù),而不希望在合并的時(shí)候存在渲染更新。
如果有一個(gè)瀏覽器認(rèn)為不會(huì)從渲染更新中受益的嵌套的瀏覽器上下文B,那么從docs中移除那些瀏覽器上下文為B的元素。
正如頂級(jí)瀏覽器上下文一樣,對(duì)于嵌套的瀏覽器行下午,很多因素也會(huì)影響到它是否會(huì)從更新渲染中受益。例如,瀏覽器也許希望花費(fèi)較少的資源渲染第三方的內(nèi)容,特別是當(dāng)前不可見(jiàn)的內(nèi)容或者是受限制的內(nèi)容。在這一的例子中,瀏覽器也許會(huì)決定很少或者根本不對(duì)這些內(nèi)容更新渲染。
對(duì)于docs中每個(gè)fully active的Document對(duì)象,觸發(fā)resize
對(duì)于docs中每個(gè)fully active的Document對(duì)象,觸發(fā)scroll
對(duì)于docs中每個(gè)fully active的Document對(duì)象,觸發(fā)媒體查詢和提交變化
對(duì)于docs中每個(gè)fully active的Document對(duì)象,運(yùn)行CSS animations并發(fā)送事件。
對(duì)于docs中每個(gè)fully active的Document對(duì)象,運(yùn)行全屏渲染步驟。
對(duì)于docs中每個(gè)fully active的Document對(duì)象,運(yùn)行animations回調(diào)函數(shù)。
對(duì)于docs中每個(gè)fully active的Document對(duì)象,更新渲染或者用戶接口,和瀏覽器上下文來(lái)反應(yīng)當(dāng)前的狀態(tài)。
9.返回到第1步繼續(xù)執(zhí)行。
接上面提到的第6步,執(zhí)行microtask checkpoint操作如下:
當(dāng)一個(gè)算法需要將一個(gè)microtask加入隊(duì)列時(shí),它必須被追加到相關(guān)的event loop的microtask 隊(duì)列。這個(gè)microtask的task source就被叫做microtask task source。
將一個(gè)microtask移動(dòng)到普通的任務(wù)隊(duì)列是很有可能的,如果發(fā)生這樣的移動(dòng)的話,在它的初次運(yùn)行時(shí),將執(zhí)行spins the event loop步驟。
當(dāng)瀏覽器去執(zhí)行一個(gè)microtask checkpoint的時(shí)候,如果這個(gè)performing a microtask checkpoint的falg為false,那么瀏覽器必須執(zhí)行以下步驟:
1.將這個(gè)flag置為true。
2.如果event loop的microtask隊(duì)列為空,則跳到第8步:
3.取出microtask隊(duì)列頭的元素。
4.將event loop的當(dāng)前運(yùn)行任務(wù)設(shè)置為上一步取出的task。
5.運(yùn)行這個(gè)task。
注意:這也許會(huì)涉及調(diào)用回調(diào)函數(shù),最后會(huì)調(diào)用清理步驟,在清理步驟中也許又會(huì)執(zhí)行microtask checkpoint操作,導(dǎo)致無(wú)終止條件的遞歸,這就是為什么我們需要用這個(gè)flag去避免這一情況。
6.設(shè)置event loop的當(dāng)前運(yùn)行任務(wù)為null。
7.從microtask隊(duì)列中移除上面運(yùn)行的這個(gè)task。然后返回到第2步。
8.對(duì)于每一個(gè)responsible event loop為這個(gè)event loop的環(huán)境配置對(duì)象,notify about rejected promises。
9.將flag置為false。
Timeline相關(guān)就像我們用迅雷同時(shí)下載10個(gè)文件一樣,假設(shè)我們是下行速度是1M/s,那么顯然不可能10個(gè)資源每個(gè)的下載速度都是100kb/s,因?yàn)槊總€(gè)資源的資源熱度是不同的,所以有的是500kb/s,而有的可能只有20kb/s,有的甚至無(wú)法下載。
對(duì)于瀏覽器而已也是類(lèi)似的道理,瀏覽器的資源調(diào)度算法以及每個(gè)時(shí)間段的網(wǎng)絡(luò)情況決定了下載資源的順序,所花費(fèi)的精力等等。以chrome的資源獲取優(yōu)先級(jí)算法為例,我們不難看出,在獲取到html之后,css的請(qǐng)求優(yōu)先級(jí)是最高的,因?yàn)閷?duì)于現(xiàn)在的web頁(yè)面來(lái)說(shuō),沒(méi)有css的后果可能遠(yuǎn)遠(yuǎn)大于沒(méi)有其他資源。對(duì)于腳本中發(fā)起的請(qǐng)求如通過(guò)接口獲取數(shù)據(jù)等則為high,對(duì)于普通的js而已,優(yōu)先級(jí)為medium,普通的圖片和async腳本都為low等等等等,隨著時(shí)間的推移,這個(gè)算法肯定也會(huì)發(fā)生相應(yīng)的變化來(lái)提升那個(gè)時(shí)候的應(yīng)用體驗(yàn)。
關(guān)于這些點(diǎn)在network中與之相關(guān)的莫過(guò)于Queueing和Stalled屬性了:
Queueing. The browser queues requests when:
There are higher priority requests.
There are already six TCP connections open for this origin, which is the limit. Applies to HTTP/1.0 and HTTP/1.1 only.
Stalled: The request could be stalled for any of the reasons described in Queueing.
所以瀏覽器最開(kāi)始會(huì)按照html中資源出現(xiàn)的順序發(fā)送請(qǐng)求去獲取,但是資源的接收順序卻不一定是按照這個(gè)順序。一個(gè)請(qǐng)求發(fā)出去之后,后面又來(lái)了一個(gè)請(qǐng)求,而這個(gè)請(qǐng)求的優(yōu)先級(jí)比當(dāng)前的要高,那么很可能就會(huì)先去接收這個(gè)優(yōu)先級(jí)更高的資源的數(shù)據(jù)。而對(duì)于優(yōu)先級(jí)相同的多個(gè)資源,則很可能采用你接收一段數(shù)據(jù),我接收一段數(shù)據(jù)這樣的方式交叉運(yùn)行。也就是我們常常看到的頁(yè)面中的圖片加載的時(shí)候往往是多個(gè)圖片同時(shí)慢慢從白屏到加載完畢,而不是一個(gè)加載完畢后再加載另一個(gè)。
另外前面已經(jīng)提到過(guò)了,對(duì)于普通腳本則是肯定會(huì)按照html中的順序執(zhí)行的,也就是說(shuō)如果腳本a只有500kb,而在他后面的腳本b只有1kb,那么即使腳本b獲取全部字節(jié)后完成finish load也不能立即執(zhí)行,必須等到腳本a獲取全部字節(jié)后且執(zhí)行完畢后它才能執(zhí)行。而如果a和b都是async的腳本化則不必遵循這一原則,誰(shuí)先獲取到誰(shuí)就先執(zhí)行。為什么呢,因?yàn)閍sync設(shè)計(jì)的本意就是為了抽離與頁(yè)面無(wú)關(guān)的邏輯的,它們之間也不應(yīng)該存在連貫性和依賴性,而后面的普通腳本更不用說(shuō)了,更不應(yīng)依賴它們?nèi)スぷ鳌?/p>
所以后面鏈接提到的視頻中提問(wèn)者說(shuō)只要不操作dom和獲取dom,就應(yīng)該把這些公共代碼提取出來(lái)放在head中async引入來(lái)達(dá)到性能優(yōu)化的效果,其實(shí)是不妥當(dāng)?shù)摹1热鏻oadsh就符合這個(gè)要求,我們顯然不能這么做,一是因?yàn)閘odash體積太大,無(wú)法保證在body尾部用到lodash的代碼所處的腳本一定晚于lodash后執(zhí)行,二是由于網(wǎng)絡(luò)原因,就是lodash是一個(gè)只有1kb的資源,也很難保證。
寫(xiě)在結(jié)尾這次閱讀規(guī)范的過(guò)程,了解了很多知識(shí),也早已超出了當(dāng)初想要獲得的知識(shí),這便是學(xué)習(xí)的樂(lè)趣。當(dāng)然也有很多地方花了很長(zhǎng)時(shí)間才弄清楚到底是表達(dá)的什么意思,也還存留一些問(wèn)題到目前也仍未理解,大家有不明白或者覺(jué)得錯(cuò)誤的地方希望多多交流,也希望隨著歲月,再來(lái)回頭探索的時(shí)候能夠明白。
閱讀更多the-javascript-event-loop-explained
從Chrome源碼看瀏覽器如何構(gòu)建DOM樹(shù)
從Chrome源碼看瀏覽器的事件機(jī)制
瀏覽器如何構(gòu)建dom樹(shù)(chrome官方文檔,另外里面有配套的視頻,非常不錯(cuò))
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/50410.html
摘要:瀏覽器推遲事件直到所有的腳本都處于狀態(tài)。解析器將處理執(zhí)行這個(gè)腳本。創(chuàng)建這個(gè)腳本的解析器的文檔有正在阻塞腳本執(zhí)行腳本元素為等待解析阻塞的腳本的狀態(tài),同一時(shí)刻只能有一個(gè)這樣的腳本存在。解析器將一個(gè)或多個(gè)字符轉(zhuǎn)換為表并處理,這個(gè)過(guò)程是一個(gè)典型的。 前言 本文主要對(duì)W3C規(guī)范中關(guān)于script標(biāo)簽和event loop相關(guān)的篇幅做了簡(jiǎn)單的探討,針對(duì)一些必要的相關(guān)概念進(jìn)行了適當(dāng)?shù)臉?biāo)注和說(shuō)明。雖然...
摘要:規(guī)范中定義了瀏覽器何時(shí)進(jìn)行渲染更新,了解它有助于性能優(yōu)化。結(jié)合一些資料,對(duì)上邊規(guī)范給出一些理解有誤請(qǐng)指正每個(gè)線程都有自己的。列為,列為,列為。我們都知道是單線程,渲染計(jì)算和腳本運(yùn)行共用同一線程網(wǎng)絡(luò)請(qǐng)求會(huì)有其他線程,導(dǎo)致腳本運(yùn)行會(huì)阻塞渲染。 本文轉(zhuǎn)自blog 轉(zhuǎn)載請(qǐng)注明出處 異步的思考 event loops隱藏得比較深,很多人對(duì)它很陌生。但提起異步,相信每個(gè)人都知道。異步背后的靠山就是...
平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動(dòng)圖演示) 為什么知乎前端圈普遍認(rèn)為H5游戲和H5展示的JSer 個(gè)人整理和封裝的YU.js庫(kù)|中文詳細(xì)注釋|供新手學(xué)習(xí)使用 擴(kuò)展JavaScript語(yǔ)法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...
平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動(dòng)圖演示) 為什么知乎前端圈普遍認(rèn)為H5游戲和H5展示的JSer 個(gè)人整理和封裝的YU.js庫(kù)|中文詳細(xì)注釋|供新手學(xué)習(xí)使用 擴(kuò)展JavaScript語(yǔ)法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...
閱讀 2423·2021-11-16 11:44
閱讀 1884·2021-10-12 10:12
閱讀 2177·2021-09-22 15:22
閱讀 3013·2021-08-11 11:17
閱讀 1512·2019-08-29 16:53
閱讀 2659·2019-08-29 14:09
閱讀 3481·2019-08-29 14:03
閱讀 3310·2019-08-29 11:09