[WebKit] JavaScriptCore解析--基础篇(四) 页面解析与JavaScript元素的执行

简介: 很多地方都已经介绍了JavaScript在浏览器是如何被执行的,这里介绍一下WebKit是如何实现的。主要涉及JS的async,defer及普通脚本的解析与执行过程的代码实现。

很多地方都已经介绍了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的定义进一步阅读代码。

     http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html

  ScriptElement::prepareScript() -> http://dev.w3.org/html5/spec/Overview.html#prepare-a-script 


WebKit在重要函数里也标注了对应标准文档的章节,看代码前可以先阅读这部分的说明。



算是做个笔记吧,有时间再进一步研究。


转载请注明出处:http://blog.csdn.net/horkychen

参考:WebKit研究

相关的UML图可以到这里下载 :WebKit Documentation on GitHub


系列索引:

基础篇 (一)JSC与WebCore

基础篇(二)解释器基础与JSC核心组件

基础篇(三)从脚本代码到JIT编译的代码实现

基础篇(四) 页面解析与JavaScript元素的执行

高级篇(一) SSA (static single assignment)

高级篇(二) 类型推导(Type Inference)

高级篇(三) Register Allocation & Trampoline




 

目录
相关文章
|
7天前
|
JavaScript 前端开发 容器
AJAX载入外部JS文件到页面并让其执行的方法(附源码)
AJAX载入外部JS文件到页面并让其执行的方法(附源码)
11 0
|
26天前
|
JavaScript 前端开发 Java
springboot从控制器请求至页面时js失效的解决方法
springboot从控制器请求至页面时js失效的解决方法
15 0
springboot从控制器请求至页面时js失效的解决方法
|
26天前
|
JavaScript 前端开发
JavaScript操作DOM元素
JavaScript操作DOM元素
11 1
|
1月前
|
JavaScript 前端开发
JavaScript如何遍历表单元素?
JavaScript如何遍历表单元素?
|
1月前
|
Web App开发 前端开发 JavaScript
编程笔记 html5&css&js 004 我的第一个页面
编程笔记 html5&css&js 004 我的第一个页面
|
26天前
|
JavaScript 前端开发
springboot+layui从控制器请求至页面时js失效的解决方法
springboot+layui从控制器请求至页面时js失效的解决方法
15 0
|
14天前
|
JavaScript 前端开发
JS:如何创建新元素并添加到页面中
JS:如何创建新元素并添加到页面中
21 1
|
21天前
|
JSON JavaScript 数据格式
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
77 2
|
1月前
|
JSON JavaScript 前端开发
JavaScript随手笔记---数组中相同的元素进行分组(数据聚合) groupBy函数
JavaScript随手笔记---数组中相同的元素进行分组(数据聚合) groupBy函数
|
1月前
|
JavaScript 前端开发
JS获取DOM元素的八种方法(含代码,简单易懂)
JS获取DOM元素的八种方法(含代码,简单易懂)

推荐镜像

更多