javascript
google浏览器javascript没反应_浏览器之导航这件小事
在這篇文章中,我們會(huì)深入的了解當(dāng)瀏覽器為用戶(hù)呈現(xiàn)一個(gè)頁(yè)面時(shí),這些進(jìn)程和線(xiàn)程之間是如何進(jìn)行通信的。
讓我們以一個(gè)常見(jiàn)的例子作為起點(diǎn):輸入一個(gè) url,瀏覽器會(huì)從服務(wù)端獲取數(shù)據(jù)并將頁(yè)面展示出來(lái)。本文會(huì)聚焦在用戶(hù)通過(guò)瀏覽器向一個(gè)站點(diǎn)發(fā)起訪(fǎng)問(wèn)請(qǐng)求以及瀏覽器準(zhǔn)備渲染這個(gè)頁(yè)面的部分,這個(gè)過(guò)程我稱(chēng)之為導(dǎo)航。
以一個(gè)瀏覽器進(jìn)程為起點(diǎn)
我們?cè)谏弦黄恼轮刑徇^(guò),所有處于窗口之外的部分都由同一個(gè)瀏覽器進(jìn)程進(jìn)行掌管。瀏覽器的進(jìn)程又同時(shí)擁有許多線(xiàn)程,掌管瀏覽器的不同部分:UI 線(xiàn)程用來(lái)繪制頂部的操作按鈕和輸入框、網(wǎng)絡(luò)線(xiàn)程負(fù)責(zé)處理并接收來(lái)自互聯(lián)網(wǎng)的數(shù)據(jù)、存儲(chǔ)線(xiàn)程控制著訪(fǎng)問(wèn)本地文件的權(quán)限等。當(dāng)你將一個(gè)網(wǎng)站的 url 輸入到瀏覽器的地址欄時(shí),此刻正是瀏覽器進(jìn)程中的 UI 線(xiàn)程在起作用。
一次簡(jiǎn)單的導(dǎo)航
Step 1:處理用戶(hù)輸入
當(dāng)用戶(hù)開(kāi)始在地址欄輸入時(shí),UI 線(xiàn)程首先會(huì)問(wèn):“大兄弟,你輸入的是個(gè)查詢(xún)字符串還是網(wǎng)站地址?”。因?yàn)?Chrome 的地址欄同時(shí)還是個(gè)搜索框,所以 UI 線(xiàn)程需要解析用戶(hù)的輸入,才能決定該直接訪(fǎng)問(wèn)網(wǎng)址還是把用戶(hù)的輸入丟給搜索引擎處理。
Step 2:開(kāi)始導(dǎo)航
當(dāng)用戶(hù)按下回車(chē)鍵后,UI 線(xiàn)程要求網(wǎng)絡(luò)線(xiàn)程去獲取網(wǎng)站的內(nèi)容。窗口的 Tab 上會(huì)開(kāi)始轉(zhuǎn)菊花,網(wǎng)絡(luò)線(xiàn)程會(huì)采用一系列的協(xié)議和操作(比如 DNS)查詢(xún)必要的信息并為請(qǐng)求建立連接。
此時(shí),網(wǎng)絡(luò)線(xiàn)程可能會(huì)收到來(lái)自服務(wù)器的一個(gè)標(biāo)記著重定向指令的頭部比如 HTTP 301,在這種情況下,網(wǎng)絡(luò)線(xiàn)程會(huì)把這件事情告訴 UI 線(xiàn)程,之后則會(huì)發(fā)起一次指向重定向地址的新的網(wǎng)絡(luò)請(qǐng)求。
Step 3:讀取響應(yīng)
當(dāng)響應(yīng)的數(shù)據(jù)開(kāi)始傳送到瀏覽器時(shí),網(wǎng)絡(luò)線(xiàn)程會(huì)在必要的情況下檢查一些來(lái)自響應(yīng)的字段。響應(yīng)數(shù)據(jù)的?Content-Type?字段會(huì)表示當(dāng)前返回的是哪種類(lèi)型的數(shù)據(jù),但它也不完全靠譜,經(jīng)常會(huì)出現(xiàn)丟失或者干脆不準(zhǔn)確的情況,但也不用擔(dān)心,MIME 嗅探[3]會(huì)完成缺失的工作。正如源碼[4]的注釋中寫(xiě)道,這是一個(gè)可以被解釋為 hack 的方案,如果感興趣的話(huà),你也可以去閱讀這些注釋,這樣就能了解不同的瀏覽器是如何將實(shí)際的數(shù)據(jù)與?Content-Type?匹配了。
如果響應(yīng)數(shù)據(jù)是一個(gè) HTML 文件,那么接下來(lái)的一步會(huì)是把數(shù)據(jù)傳遞給瀏覽器的渲染進(jìn)程;但如果數(shù)據(jù)是 zip 壓縮文件或其他類(lèi)型的文件,意味著這將被定位成一次下載動(dòng)作,于是瀏覽器會(huì)將數(shù)據(jù)轉(zhuǎn)交給下載管理器去處理。
通常這一步也是安全檢測(cè)[5]發(fā)生的時(shí)候:如果域名或響應(yīng)數(shù)據(jù)和已知的惡意網(wǎng)站匹配時(shí),網(wǎng)絡(luò)進(jìn)程會(huì)拋出一個(gè)警告,并展現(xiàn)一個(gè)告警的頁(yè)面。另外,CORB[6]?檢測(cè)也會(huì)開(kāi)始工作,確保那些來(lái)自敏感站點(diǎn)的跨站響應(yīng)數(shù)據(jù)不會(huì)進(jìn)入到瀏覽器的渲染進(jìn)程中。
Step 4:渲染進(jìn)程
網(wǎng)絡(luò)線(xiàn)程以獲取了全部的數(shù)據(jù),并完成了所有需要的檢查,此刻它自信的告訴 UI 線(xiàn)程:“小兄弟,數(shù)據(jù)準(zhǔn)備好了!”。接著,UI 線(xiàn)程會(huì)喚起一個(gè)渲染進(jìn)程去渲染頁(yè)面。
由于網(wǎng)絡(luò)情況的不可控,一個(gè)請(qǐng)求可能會(huì)花上好幾百毫秒才能把響應(yīng)數(shù)據(jù)拿回來(lái),所以這里瀏覽器默認(rèn)開(kāi)啟了用來(lái)加速這一過(guò)程的優(yōu)化。在 Step 2 中,當(dāng) UI 線(xiàn)程將需要請(qǐng)求的 url 告訴網(wǎng)絡(luò)線(xiàn)程時(shí),其實(shí)它本身已經(jīng)知道要導(dǎo)航到哪個(gè)網(wǎng)站了,于是 UI 線(xiàn)程在把 url 傳遞給網(wǎng)絡(luò)線(xiàn)程的同時(shí),會(huì)嘗試啟動(dòng)一個(gè)渲染進(jìn)程。如果一切都按照預(yù)期正常進(jìn)行的話(huà),當(dāng)網(wǎng)絡(luò)線(xiàn)程拿到數(shù)據(jù)時(shí),渲染進(jìn)程就已經(jīng)處于待命狀態(tài)了。也會(huì)有例外的情況:比如導(dǎo)航重定向到一個(gè)另外的站點(diǎn),那么預(yù)先啟動(dòng)好的渲染進(jìn)程將不會(huì)被使用,這導(dǎo)致 UI 線(xiàn)程需要重新啟動(dòng)一個(gè)渲染進(jìn)程。
Step 5:觸發(fā)導(dǎo)航
現(xiàn)在我們假設(shè)數(shù)據(jù)和渲染進(jìn)程都準(zhǔn)備好了,瀏覽器進(jìn)程通過(guò) IPC 告知渲染進(jìn)程可以出發(fā)本次導(dǎo)航了。與此同時(shí),數(shù)據(jù)流也將傳遞給渲染進(jìn)程,這樣后者就能繼續(xù)接收 HTML 數(shù)據(jù)。一旦瀏覽器收到了來(lái)自渲染進(jìn)程的導(dǎo)航啟動(dòng)信號(hào),這次導(dǎo)航也就完成了,下一步進(jìn)入文檔的加載階段。
到這會(huì)兒,瀏覽器的地址欄更新,安全指示符和站點(diǎn)的設(shè)置 UI 會(huì)將新頁(yè)面的信息呈現(xiàn)出來(lái)。當(dāng)前窗口的 session 將會(huì)更新,剛導(dǎo)航到的頁(yè)面會(huì)被后退/前進(jìn)按鈕記錄到窗口的頁(yè)面歷史中。為了便于在關(guān)閉窗口時(shí)恢復(fù)頁(yè)面,歷史的會(huì)話(huà)記錄會(huì)保存在本地的磁盤(pán)上。
Extra Step:初始加載完成
當(dāng)導(dǎo)航觸發(fā)后,渲染進(jìn)程會(huì)持續(xù)接收資源并渲染頁(yè)面。我們將在下一篇文章中討論這一步的更多細(xì)節(jié)。當(dāng)渲染進(jìn)程“完成”渲染后,它會(huì)通過(guò) IPC 告知瀏覽器進(jìn)程(頁(yè)面的 onload 事件均已執(zhí)行完畢后),UI 線(xiàn)程也就不再在 tab 上轉(zhuǎn)菊花了。
上面的“完成”兩個(gè)字,之所以打了雙引號(hào),因?yàn)樵趯?shí)際場(chǎng)景中,它通常并不真正意味著完成,因?yàn)榭蛻?hù)端的 JavaScript 可能在此時(shí)持續(xù)地加載資源并渲染新的視圖。
導(dǎo)航到另一個(gè)網(wǎng)站
一次簡(jiǎn)單的導(dǎo)航截至目前已經(jīng)完成了。假如這時(shí)用戶(hù)輸入了一個(gè)不同的 url 會(huì)發(fā)生什么呢?其實(shí)也沒(méi)啥,瀏覽器進(jìn)程會(huì)按照上面的步驟導(dǎo)航到這個(gè)網(wǎng)站。但在這一切開(kāi)始之前,瀏覽器會(huì)檢查當(dāng)前已經(jīng)渲染好了的網(wǎng)站是否需要在網(wǎng)頁(yè)卸載之前搞一點(diǎn)事情,這就是?beforeunload?事件。
在?beforeunload?事件中,我們可以在用戶(hù)即將跳轉(zhuǎn)至其他頁(yè)面或者關(guān)閉 Tab 的時(shí)候發(fā)起一個(gè)“確認(rèn)離開(kāi)當(dāng)前頁(yè)面?”的二次確認(rèn)。Tab 中的所有東西都由渲染進(jìn)程控制著,當(dāng)然也包括開(kāi)發(fā)者編寫(xiě)的 JavaScript,所以當(dāng)一個(gè)新的導(dǎo)航請(qǐng)求即將到來(lái)時(shí),瀏覽器進(jìn)程會(huì)對(duì)當(dāng)前的渲染進(jìn)程做最后的檢查。
我們應(yīng)當(dāng)盡量避免在?beforeunload?中添加總會(huì)執(zhí)行的事件代碼,這會(huì)造成更多的交互延時(shí),畢竟它們總會(huì)在新的導(dǎo)航開(kāi)始之前執(zhí)行。只在需要的時(shí)候添加這些代碼,比如提醒用戶(hù)如果進(jìn)入新的頁(yè)面那么當(dāng)前頁(yè)面的數(shù)據(jù)會(huì)丟失。
如果導(dǎo)航是在渲染進(jìn)程中被創(chuàng)建的(比如用戶(hù)點(diǎn)擊了頁(yè)面上的某一鏈接或者在 JavaScript 運(yùn)行了?window.location.href = 'https://kyrieliu.cn'?),則當(dāng)前的渲染進(jìn)程會(huì)首先檢查是?beforeunload?中是否有東西需要執(zhí)行。之后,它會(huì)經(jīng)歷與瀏覽器進(jìn)程直接發(fā)起導(dǎo)航后一樣的導(dǎo)航過(guò)程。
當(dāng)新的導(dǎo)航將發(fā)往與當(dāng)前頁(yè)面不同的站點(diǎn)時(shí),瀏覽器將會(huì)創(chuàng)建一個(gè)新的渲染進(jìn)程去處理這些新工作,舊的渲染進(jìn)程則則用來(lái)在剩余的時(shí)間里處理諸如?unload?的頁(yè)面事件。如果你想了解更多的話(huà),可以看看頁(yè)面生命周期概覽[7]和頁(yè)面生命周期 API[8]這兩篇文章。
如果有 Service Worker...
Service Worker[9]?的引入會(huì)對(duì)頁(yè)面的導(dǎo)航流程帶來(lái)一些改變。Service Worker 是一種可以在應(yīng)用代碼中編寫(xiě)網(wǎng)絡(luò)代理的方法;增強(qiáng)了開(kāi)發(fā)者對(duì)于本地緩存以及何時(shí)發(fā)起網(wǎng)絡(luò)請(qǐng)求的控制。如果 Service Worker 提前設(shè)置了從本地緩存中讀取某一頁(yè)面的數(shù)據(jù),那么也就不需要發(fā)起網(wǎng)絡(luò)請(qǐng)求了。
需要明確的一點(diǎn)是,即使 Service Worker 提供了聽(tīng)起來(lái)很高端的功能,但它實(shí)質(zhì)上也是運(yùn)行在渲染進(jìn)程中的 JavaScript 代碼。那么問(wèn)題來(lái)了:當(dāng)用戶(hù)發(fā)起一次導(dǎo)航時(shí),瀏覽器進(jìn)程是如何知道目標(biāo)站點(diǎn)存在一個(gè) Service Worker 的呢?
當(dāng)一個(gè) Service Worker 注冊(cè)后,它的作用域會(huì)保存在一個(gè)引用中(你可以通過(guò)?Service Worker 的生命周期[10]?這篇文章了解我所說(shuō)的“作用域”)。當(dāng)導(dǎo)航發(fā)生時(shí),網(wǎng)絡(luò)線(xiàn)程會(huì)依據(jù)域名在已注冊(cè)的 Service Worker 作用域集合中查詢(xún),如果找到某個(gè)對(duì)應(yīng)的 Service Worker,UI 線(xiàn)程會(huì)發(fā)起一個(gè)渲染進(jìn)程去執(zhí)行 Service Worker 中的代碼。Service Worker 可以從本地緩存中加載數(shù)據(jù)(無(wú)需發(fā)起網(wǎng)絡(luò)請(qǐng)求),也可以選擇通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取最新的資源和數(shù)據(jù)。
導(dǎo)航預(yù)加載
相信你可以發(fā)現(xiàn),如果 Service Worker 最終決定從網(wǎng)絡(luò)中請(qǐng)求數(shù)據(jù),那么之前在瀏覽器進(jìn)程和渲染進(jìn)程之間所發(fā)生的通信都將成為導(dǎo)致響應(yīng)延時(shí)的罪魁禍?zhǔn)住?dǎo)航預(yù)加載[11]就是用來(lái)加速這一進(jìn)程的機(jī)制:與 Service Worker 并行啟動(dòng)去加載資源。它將為這些請(qǐng)求設(shè)置一個(gè) Header,由服務(wù)端來(lái)決定為這些請(qǐng)求發(fā)送不同的內(nèi)容;比如,僅返回更新的數(shù)據(jù)而不是整個(gè)文檔。
總結(jié)
在這篇文章中,我們檢視了在導(dǎo)航時(shí)都發(fā)生了什么,以及 Web 應(yīng)用的代碼比如響應(yīng)頭和客戶(hù)端的 JavaScript 代碼是如何與瀏覽器進(jìn)行交互的。了解了瀏覽器是如何一步步從網(wǎng)絡(luò)中請(qǐng)求數(shù)據(jù)的,這能讓我們更好的理解很多 API 比如導(dǎo)航預(yù)加載的誕生初衷。
參考資料
[1]Inside Look at Modern Web Browser (part 2):?https://developers.google.com/web/updates/2018/09/inside-browser-part2
[2]Mariko Kosaka:?https://developers.google.com/web/resources/contributors/kosamari
[3]MIME 嗅探:?https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
[4]源碼:?https://cs.chromium.org/chromium/src/net/base/mime_sniffer.cc?sq=package:chromium&dr=CS&l=5
[5]安全檢測(cè):?https://safebrowsing.google.com/
[6]CORB:?https://www.chromium.org/Home/chromium-security/corb-for-developers
[7]頁(yè)面生命周期概覽:?https://developers.google.cn/web/updates/2018/07/page-lifecycle-api#overview_of_page_lifecycle_states_and_events
[8]頁(yè)面生命周期 API:?https://developers.google.cn/web/updates/2018/07/page-lifecycle-api
[9]Service Worker:?https://developers.google.cn/web/fundamentals/primers/service-workers
[10]Service Worker 的生命周期:?https://developers.google.cn/web/fundamentals/primers/service-workers/lifecycle
[11]導(dǎo)航預(yù)加載:?https://developers.google.cn/web/updates/2017/02/navigation-preload
總結(jié)
以上是生活随笔為你收集整理的google浏览器javascript没反应_浏览器之导航这件小事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python编程规范 谷歌_Python
- 下一篇: seata 集群_【视频】 聊聊分布式事