[WebKit] JavaScriptCore解析--基礎篇(四) 頁面解析與JavaScript元素的執行
很多地方都已經介紹了JavaScript在瀏覽器是如何被執行的,這裡介紹一下WebKit是如何實現的。主要涉及JS的async,defer及普通指令碼的解析與執行過程的程式碼實現。
1. 概要說明
先概要說明一下瀏覽器如何執行JavaScript的。 首先瀏覽器的頁面解析器(Document Parser)遇到<script>就會發起下載(指令碼內容在頁面內的就不用下載了)。然後針對不同情況執行的方式有所不同:
. async (在script標籤中啟用了async屬性)
這是非同步執行,下載時不會阻塞Document Parser, 當JavaScript被載入完成後就會開始執行。
. defer (在script標籤中啟用了defer屬性)
這個是推遲執行,下載時同樣不會阻塞Document Parser, 會放到Document Parser完成頁面解析後才會執行。
. 對於引用外部的指令碼檔案,下載時Document Parser會被阻塞至指令碼執行完。
. 如果頁面中有Style Sheet還沒有解析成功, 則指令碼會被阻塞直到Style Sheet下載並解析完成。
因為阻塞會嚴重影響頁面解析的效能,所以也是瀏覽器優化的重點:
a. WebKit引入Preload Scanner允許在下載時嘗試繼續處理後面的資源。 [LINK] 不過隨著網路的提速,這個優化的效果應逐漸減弱。
b. FireFox實現非同步HTML解析,WebKit也2013年年初實現了一個輕量級版本(只將tokenizer多執行緒化)。
2. WebCore中執行JavaScript
2.1 主要功能劃分
WebCore中關於JavaScript的元素的解析與執行,個人把相關類分成兩個主要的類別:
頁面解析功能和JS執行功能。
頁面解析類別的側重於在HTMLDocumentParser解析頁面過程中管理指令碼的建立、規劃執行策略等。這一部分細節功能比較複雜,在W3C中有專門的定義,WebKit中的實現很多地方也註上了所參考的章節。
JS執行類別基於Document和Frame來管理JS執行環境,並同JSC協作執行JS指令碼。
下面是一個總覽, 黃色背景的類屬於為頁面解析類別,紅色背景的部分則歸到執行類別。
*主要類的程式碼基本集中在bindings/js目錄下。
2.2 JS元素的執行
這裡的執行不包括頁面對JS執行的策略,主要針對具體的執行過程。
JSMainThreadExecState 負責最終對接JSC模組執行JS指令碼。在系統中是一個單例的物件,也就是保證單程序與JSC協作。這同樣也是JS的一個限制點。
ScriptController 維護一個JavaScriptCore執行環境,在必要時呼叫JSMainThreadExecState執行。
ScriptElement 代表了一個具體的script元素,包括JavaScript, SVG等。
再附上一張時序圖來幫助瞭解它們間的關係, (其中指令碼的執行決策是由HTMLScriptRunner來發起的):
2.3 JS元素的解析與管理
在WebKit內部,頁面解析與指令碼控制的操作主要是在HTMLDocumentParser,HTMLScriptRunner和ScriptElement中實現的。
2.3.1 JS解析
從下面這張經典的圖說起:
這張圖源自W3C的官方定義, 與主文相關的是說明JS之所以會導致頁面解析阻塞正是因為它允許指令碼使用document.write()改變頁面內容,會造成DOM樹可能會需要重新解析(reparser)。所以非但瀏覽器要做些優化,JS開發者也要注意JS的實現方法,比如編寫non-block script。
另外不但JS阻塞Parser, Style Sheet也會阻塞JS。如果JS裡包含了對某個元素的顯示屬性的檢測,在CSS還沒有載入解析完成的情況下,肯定得不到正確結果。所以對應在WebKit也對應出了不同的定義。
在HTMLScriptRunner有兩個重要的成員變數:
m_scriptsToExecuteAfterParsing -> 由requestDeferredScript()新增。
m_parserBlockingScript -> 由requestParsingBlockingScript新增。
根據名字也可以看出來前者是儲存defer指令碼,後者是儲存一般的指令碼的。至於async則是在下載完成後觸發的,稍後說明。
2.4 JS執行策略
之前說到不同的JS載入屬性的執行方法會不一樣:
async -> 是由ScriptRunner::notifyScriptReady在指令碼下載完成時執行的。
defer -> 是由HTMLDocumentParser::notifyFinished()在頁面解析完成時發起執行的。
其它的JS指令碼則在處理token時由HTMLDocumentParser::canTakeNextToken來檢測併發起執行的。
2.4.1一般JS指令碼的執行
一般沒有帶async和defer屬性的JS指令碼,被稱為解析阻塞指令碼(Parsing Blocking Scripts),則由HTMLScriptRunner::executeParsingBlockingScripts()負責執行的。
一個典型的呼叫方式如下:
比如當新解析到一個Token時,Document Parser就會觸發一個canTakeNextToken來檢查一些狀態,其中一項就是看看指令碼是否可以執行,如果條件合適就會執行相應的指令碼。所謂條件合適,可以在它會呼叫的HTMLScriptRunner::isPendingScriptReady裡看到(函式也很好理解,就是看之前掛起的指令碼是不是又可以執行了) :
bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
{
m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
if (m_hasScriptsWaitingForStylesheets)
return false;
if (script.cachedScript() && !script.cachedScript()->isLoaded())
return false;
return true;
}
其中等待的就是兩個條件:
1. 是不是正在載入CSS.
2. 指令碼是不是有信賴的指令碼還在載入.
下面再介紹一個入口。因為有一部分指令碼是等待CSS載入完成後才會執行的,所以在CSS載入完成會一次呼叫過程如下:
2.4.2 async與defer指令碼的執行
下面就附兩張圖來說明兩者的執行過程,中間省略一些不重要的步驟。
async的執行過程, 注意是由資源載入觸發:
defer指令碼的執行過程,除了這個之外,還有一些異常的考慮,比如在HTMLScriptRunner析構時也會進行處理。
2.4.3 進一步閱讀程式碼指引
以HTMLScriptRunner的runScript為例,它是由HTMLScriptRunner::execute()呼叫的,其實對應於W3C定義的標準流程: Running a script:
這一部分的涉及到很多的細節,可以參考W3C的定義進一步閱讀程式碼。
WebKit在重要函式裡也標註了對應標準文件的章節,看程式碼前可以先閱讀這部分的說明。
算是做個筆記吧,有時間再進一步研究。
系列索引:
相關推薦
[WebKit] JavaScriptCore解析--基礎篇(四) 頁面解析與JavaScript元素的執行
很多地方都已經介紹了JavaScript在瀏覽器是如何被執行的,這裡介紹一下WebKit是如何實現的。主要涉及JS的async,defer及普通指令碼的解析與執行過程的程式碼實現。 1. 概要說明 先概要說明一下瀏覽器如何執行JavaScript的。 首先瀏覽器的頁面解析
[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現
前面說了一些解析、生成ByteCode直至JIT的基本概念,下面是對照JavaScriptCore原始碼來大致瞭解它的實現。 從JS Script到Byte Code 首先說明Lexer, Parser和ByteCode的生成都是由ProgramExecutable初始化
[WebKit] JavaScriptCore解析--基礎篇(二)直譯器基礎與JSC核心元件
這一篇主要說明直譯器的基本工作過程和JSC的核心元件的實現。 作為一個語言,就像人在的平時交流時一樣,當接收到資訊後,包含兩個過程:先理解再行動。理解的過程就是語言解析的過程,行動就是根據解析的結果執行對應的行為。在計算機領域,理解就是編譯或解釋,這個已經被研究的很透徹了
[WebKit核心] JavaScriptCore深度解析--基礎篇(一)位元組碼生成及語法樹的構建詳情分析
看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。 JS
[WebKit核心] JavaScript引擎深度解析--基礎篇(一)位元組碼生成及語法樹的構建詳情分析
看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。 JSC對
PE檔案解析 基礎篇
PE檔案解析 基礎篇 來源 https://bbs.pediy.com/thread-247114.htm 前言 之前學習了PE格式,為了更好的理解,決定寫一個類似LoadPE的小工具。 編譯器是VS2015,採用MFC框架。 此係列文章採用邊介紹知識點,邊寫程式碼的
基礎篇:深入解析JAVA註解機制
[TOC](目錄標題) # java實現註解的底層原理和概念 - java註解是JDK1.5引入的一種註釋機制,java語言的類、方法、變數、引數和包都可以被註解標註。和Javadoc不同,java註解可以通過反射獲取標註內容 - 在編譯器生成.class檔案時,註解可以被嵌入位元組碼中,而jvm也可以保
Java面試題-基礎篇四
規範 error 一行代碼 不必要 表達 loading 需求 exception 引用 31、String s = new String("xyz");創建了幾個StringObject?是否可以繼承String類? 兩個或一個都有可能,”xyz”
解析庫的使用---頁面解析總結
css樣式 語法 語言 -- 選擇 family 使用 css樣式選擇器 XML 頁面解析總結 使用‘正則’,‘css樣式選擇器’,‘xpath(xml路徑語言)’進行頁面匹配;對相應的語法,和場景進行總結。 內容來源於: 解析庫的使用---頁面解析總結
JS 基礎篇(四):JS中的函式
目錄: 簡單介紹下關於JS函式使用過程中的一些情況。 一、函式的引數 1、呼叫函式時沒有提供足夠的引數,缺少的引數將會被underfined替代。 function add(a,b){ console.log("a:"+a); // a:1 c
基礎篇——四種啟動模式
寫程式碼的四點: 1.明確需求。要做什麼? 2.分析思路。要怎麼做?(1,2,3……) 3.確定步驟。每一個思路要用到哪些語句、方法和物件。 4.程式碼實現。用具體的語言程式碼將思路實現出來。 學習新技術的四點:
【Redis詳解基礎篇四(Redis通訊協議)】
前言 Redis通訊協議是什麼? reids通訊協議就是接受處理來自客戶端請求,非阻塞,iO複用的TCP伺服器 Protocol redis協議與TCP協議進行通訊,他們的協議術語叫做Protocol,代表了伺服器於客戶端之間的通訊,對於redis來講這種協
spring mvc基礎篇(四):後端控制器之MultiActionController
4.1簡介 Struts2裡面有種多方法action,能夠讓我們在一個類裡面處理多個請求,經過我們的配置,能讓每個請求對應一個方法。這樣,系統就減少了很多類,系統性能也會得到提升。Spring的MultiActionController也是這樣的作用。 4.2 開發環境
react基礎篇四
但是 classname 使用 ring rec change 頁面 left mes 列表 & Keys 渲染多個組件 你可以通過使用{}在JSX內構建一個元素集合 下面,我們使用Javascript中的map()方法遍歷numbers數組。對數組中的每個元
selenium測試框架篇,頁面對象和元素對象的管理
ref end dem target .cn factor imp wait 統一管理 前期已經做好使用Jenkins做buildhttp://www.cnblogs.com/tobecrazy/p/4529399.html 做自動化框架,不可避免的就是對象庫。 有一個
java並發基礎(四)--- 取消與關閉
rime ole out sys 類型 interrupt 來看 方法 發出 《java並發編程實戰》的第7章是任務的取消與關閉。我覺得這一章和第6章任務執行同樣重要,一個在行為良好的軟件和勉強運行的軟件之間的最主要的區別就是,行為良好的軟件能很完善的處理失敗、關閉和取
Python學習-基礎篇4 模塊與包與常用模塊
邏輯 zip 了解 mon get() 測試結果 python程序 rec rac 一 模塊介紹 1、什麽是模塊?#常見的場景:一個模塊就是一個包含了一組功能的python文件,比如spam.py,模塊名為spam,可以通過import spam使用。 #在python中
【Web自動化測試——代碼篇四】常用方法——常見元素操作
fin pack 百度輸入 submit .get ted baidu 百度搜 內容 瀏覽器這個大框架我們都能控制,區區頁面小元素又能奈我們何!!!之前的【Web自動化測試——代碼篇二】條條大路找元素 已經講述了許多獲取元素的方法,在此基礎上我們來認識幾個元素最常見的相關操
Scala基礎篇-02函數與代碼塊
ESS 表達 代碼塊 基礎 代碼 最終 一個 res sca 1.block 代碼塊也是表達式,其最終求得的值是最後一個表達式的值。 {exp1;exp2} { exp1 exp2 } 2.function def funtionName(param:ParamTy
C語言 指標基礎篇 陣列,函式與指標的運用 2 14
下面看看如何在函式中運用指標吧 下面是往函式傳入指標的簡單操作,不是傳入陣列的。判斷一個a是否大於b是的話給,是的話對其進行操作,不是的話就直接返回。 1 #include <stdio.h> 2 int main(){ 3 int num1,num2,*p1,