首页 > > > webkit JS引擎深入分析.pdf

webkit JS引擎深入分析.pdf

webkit JS引擎深入分析.pdf

上传者: hidoog 2012-03-04 评分1 评论0 下载839 收藏10 阅读量1712 暂无简介 简介 举报

简介:本文档为《webkit JS引擎深入分析pdf》,可适用于IT书籍领域,主题内容包含WebKitWebKitWebKitWebKit的的的的JavaScriptJavaScriptJavaScriptJavaScript引擎简介引擎符等。

WebKitWebKitWebKitWebKit的的的的JavaScriptJavaScriptJavaScriptJavaScript引擎简介引擎简介引擎简介引擎简介基于基于基于基于WebKitrWebKitrWebKitrWebKitr腾讯研究院无线中心无线浏览器组周晓波(xiaobozhou)概述浏览器浏览器是用于展示远程信息并提供有限修改能力的客户端程序。事实上世界上第一个浏览器是一个远程格式化编辑器其修改权限是很大的。而目前浏览提提供的修改能力很弱对修改的权限控制、对修改内容的处理等更多的集中在服务器端。因此可以说现在的浏览器主要任务是更高效的、更标准的处理和显示远程信息。浏览器的这些主要工作都是由内核完成的。浏览器对远程信息的显示并不是随意的因为远程信息(通常是网页)是一种格式化的信息即这些信息不仅包括内容而且包括结构和显示样式。浏览器需要根据信息的格式化指令(如html的标签)来对信息的结构进行理解理解出来的每一部分称为一个元素。所谓结构化就是信息各部分之间的层级关系类似书本的章节。然后浏览器根据样式指令(如style标签或属性等)来决定(或建议因为不一定每个指定的样式都能满足)某一元素的样式。再后浏览器根据当前视窗的大小以及元素之间的关系以各个元素的样式(如大小)为约束条件来输出每个元素的绝对位置等信息。最后调用平台相关的接口来把每个元素在屏幕上画出来。如果用户利用JavaScript改变了某个部分浏览器就重复最后三步操作。上述过程中第一步是解析标记语言其结果是形成DOM树第二步称为渲染(Render)其结果是产生Render树第三步称为布局(Layout)其结果是决定远程信息的最终样子。这三步是浏览器内核的核心功能。在每次加载一个页面时都会执行而JavaScript扮演的角色就是在不再次加载的情况下推动上述三步的执行这是通过对DOM树的修改来实现的。图展示了一个页面解析的基本过程。这里涉及到几个中间实体:页面源文件、DOM图:网页解析基本过程。树、Render树和JavaScript语法树。这里的页面源文件是广义的不是简单的一个远程文件。一方面随着动态页面的流行几乎所有的页面源文件都是在服务器端根据用户的需要生成的另外ajax技术会直接修改DOM树在页面源文件中没有任何迹象。这里页面源文件就是指所有这些从服务器获取的信息。这三个椭圆圈住的内容就是本文所要讨论的主题:Render树随DOM树的变化(包括结构的变化和节点属性的变化)而变化JavaScript修改DOM树来推动这个变化。脚本语言Script这个单词是指电影剧本是用来指示演员行动的指令这正如脚本语言:本身不做实际的事情只是指挥解释器如何做事情。可以这样理解脚本语言:它是某个程序(即脚本解释器)的命令行参数但是这个参数是如此的复杂以至于必须拥有自己的语法。这种理解方式可以说明这样几个问题:)在操作系统的角度来看只有一个程序在执行就是脚本解释器)脚本语言通过给解释器发指令来实现某个功能不同于C语言直接给操作系统发指令来实现功能)脚本语言有语法结构。如图所示一般二进制程序以操作系统为平台直接运行于操作系统提供的的进程上下文中程序的改动和发布需要针对多种操作系统。而脚本语言依赖于解释器只要保证解释器一致(同一个或者执行相同的标准)就能保证运行。这样的好处是利用解释器隔离的平台相关性为代码发布降低了难度。这非常适合Web程序的开发场景。从另一个角度也可以说脚本解释器是一个极具扩展性的软件它通过输入不同的脚本来改变其运行归根结底是一种虚拟化处理。从前面的叙述可以知道脚本语言的扩展性来源于两个方面:一是利用脚本语言的语法组合现有的功能例如定义新的函数、类和数据结构等但是这种其实只增强了易用性。另一个方面是扩展解释器提供新的功能例如Python调用C语言编写的模块。扩展解释器可以是利用其他语言(主要是CC)提供新的模块也可以是把解释器嵌入到其他软件中浏览器是后一种情形。浏览器在没有JavaScript之前就有了它只是利用JavaScript来增加动态效果因此它内嵌JavaScript解释器。站在浏览器的角度它是利用JavaScript来动态的调用某些操作来增加其扩展性。事实上有很多著名的软件都具有这种结构内嵌脚本解释器来提供扩展性。如Emacs利用Lisp脚本来扩展编辑功能NS(NetworkSimulator)利用TclOTcl来初始化网络拓扑和网络事件VIM利也定义了自己的脚本语言iptablesNetfilter也算是利用简单脚本来控制内核对数据包的处理等等。那么作为JavaScript它是如何嵌入浏览器的呢?或者说其功能是如何被扩展的呢?从前面的叙述可以看到虚拟化是为了屏蔽异质的平台为上层提供统一的接口但是如果这个虚拟化本身是多种多样的没有一个统一的标准那将没有任何屏蔽效果反而引入一场灾难幸好在浏览器技术中有DOM标准和ECMAScript标准。图:二进制程序和脚本程序的运行方式比较DOMDOMDOMDOMDOM描述了一个数据结构。文章开头提到浏览器的第一步就是要理解信息的结构。理解的过程其实并不复杂(不考虑容错)因为浏览器所支持的标记语言(如html)规定了信息的结构必须是树状的那么通过栈就可以解析出这种结构。问题在于如何存储解析出来的结构并且应该提供什么样的操作接口给其他模块如JavaScript解释器。如果这个接口不能标准化那么JavaScript解释器就很难一致。DOM就是这个对这个标准化接口的描述。DOM标准包含个级别实现的接口逐级趋向完备。另外它分为core、html、views、events、style、traversalrange、LS和Validation几个部分。core和html部分定义了很多的类型包含了所有标签及其继承关系。图是webkit对DOM标准的实现中Input标签对应的类的继承关系。和DOM标准中所描述的继承关系有差别这很容易理解:DOM只定义外界关心的接口而WebKit实现时则要考虑实现细节和代码架构而且从图中可以看到WebKit的这部分类结构把DOM标准的Events部分也实现了。有了这些类结构就可以表达文档的元素了例如Input元素(即由Input标签包含的内容)用HTMLInputElement类的实例来表达。接下来元素之间的层级关系如何表现例如form元素有两个input子元素。DOM通过定义操作接口如插入删除一个节点插入删除一个子节点查找节点等来实现元素间层级关系的构建但是DOM并没有规定这些元素应该如何存储是N叉树、数组还是其他也就是说DOM不关心元素的组织方式只关心接口。DOM也规定了StyleSheetCSS的类结构即解析出的CSS规则用什么来类的实例表达。至于这些实例放在哪里则并不关心。StyleSheetCSS是对DOM树中元素的样式的描述它只是一条条的规则元素在渲染时从中找到属于自己的那些规则就行了。图显示了WebKit中由DOM树结合CSS生成Render树的过程。需要注意的一点是CSS的存储不像DOM树那样有很强的结构它分散放在多个地方例如可以在StyleElement对象中但是DOM的根节点Document对象的mstyleSheetCandidateNodes变量指出了哪些DOM节点存储了CSS规则然后在Document::recalcStyleSelector函数中遍历这些节点中的CSS规则合成一个RefPtr<StyleSheetList>mstyleSheets这样整个文档所有的CSS规则都在Document对象中了。之后Render过程为每个节点找到其CSS规则并产生Render树。图:Input标签的继承关系WebKit实现VSDOM标准JavaScriptJavaScriptJavaScriptJavaScript解释器前面介绍的其实是JavaScript解释器所面临的环境即它被内嵌到什么中去了也即是它可利用的资源有哪些。下面来简要介绍JavaScript解释器本身。解释器眼中的JavaScriptJavaScriptJavaScriptJavaScript对象JavaScript本身是一种通用的脚本语言其基本能力是构建语法树根据语法树的结构执行相关函数。这里函数是指解释器提供的API而不是JavaScript函数事实上JavaScript解释器只认识对象(C对象即解释器内部的数据类型不是JavaScript对象)甚至把JavaScript的类型也实现为对象。JavaScript语言本身语法只具有很弱的面向对象能力需要使用JavaScript解释器提供的一种称为原型链的方式实现对象和继承。图展示了JavaScript解释器中的JSObject对象的结构每一个JavaScript对象都对应着一个JSObject对象。其中prop指向一个map结构存储的是<name,value>对即该对象的属性表而proto则指向另一个JSObject对象。proto指向的就是所谓原型(Prototype)。JavaScript中XXXyyy的表达式即告诉解释器在XXX对应的JSObject对象属性表中找yyy如果找不到则到proto指向的JSObject的属性表中找。很显然通过proto指针最终找到的属性是所有对象共有的属性而prop指向的则是该对象独有的属性。这与C的虚函数表指针正好相反。通过这种方式不仅可以实现对象共享属性(对象共享属性其实就是类的属性)还可以实现继承即把prop指向想继承的那个类的一个实例就可以访问它的属性了也就是继承自它了。图:由DOM树、CSS规则形成Render树JavaScript解释器的类都是继承自JSObject都具有图的结构。有了这个结构所有具有XXXyyy的表达式的执行过程就清楚了:通过两个字符串XXX和yyy找出对应的指针如果yyy是一个变量则返回其值如果是函数则执行这个函数。当然在实现中属性表的结构比这个复杂尤其是初始化阶段。每个继承自JSObject的类需要一个静态变量ClassInfosinfo来提供一些初始化信息以及类型信息。JavaScript解释器区分的对待sinfo提供的属性和解释器本身具有的属性:在解释如XXXyyy的表达式时如果发现yyy是一般的JavaScript属性则直接查属性表如果是由sinfo提供的属性则有callRuntimeMethod函数作为统一入口去查找sinfo(参看图)。但是从总体上看可以认为就是提供一个属性表。JavaScriptJavaScriptJavaScriptJavaScript语法树对于控制流和算术表达式的执行通过语法树来实现。对x=yz解释器创建五个对象并组织成一个树:每个对象都实现了evaluate函数通过调用根节点的evaluate可以递归调用所有节点的evaluate从而完成该表达式的求值。这只是一个简单的例子对于大量的JavaScript源代码其语法树是很复杂的。综上所述可以认为JavaScript解释器的解释过程是:首先通过扫描JavaScript源代码构建语法树然后由于函数的调用(如<script>标签里的顶层函数调用onload等事件调用)触发语法树的求值在求值过程中会执行一些由JavaScript解释器提供的API(图中prop所指向的表中有API的入口地址)。那么向prop指向的表中添加更多的API也就扩展了JavaScript解释器。这正是宿纳(host)JavaScript所要做的事情。宿纳JavaScriptJavaScriptJavaScriptJavaScript宿纳(host)这个词很形象:即招待这里就是如何招待JavaScript解释器也就是为它提供执行环境。WebKit是通过绑定(binding)机制实现JavaScript和DOM的互操作的即用DOM来招待JavaScript解释器。binding包括三个方面:一是JavaScript操作DOM即让JavaScript能访问DOM接口二是DOM中触发JavaScript语句的执行如onclick三是JavaScript执行环境即在创建语法树之前如何初始化。这一节的叙述基于WebKitr。JavaScriptJavaScriptJavaScriptJavaScript操作DOMDOMDOMDOM图:JavaScript对象在解释器中的表现形式前面提到JavaScript对象在JavaScript解释器看来都是JSObject对象不需要理解其具体类型和含义只需要proto和prop就行了。也就是说扩展JavaScript对象只需要继承JSObject就行了。为了让JavaScript能操作DOM对象如Document这里操作包括创建、访问其属性、调用成员函数等就需要实现一个JSDocument类(称之为影子类)并在构造函数中初始化的一个属性表把DOM的Document类暴露的接口的地址加进去。WebKit是通过一个脚本来自动生成所有DOM元素类XXX对应的JSXXX类的。需要注意的是JSXXX类很小它并不是XXX对象所有内容的拷贝而是用一个指针指向XXX对象来访问其成员也就是说它们只是DOM对象的wrapper。而且并不是所有的DOM对象都会在JavaScript中有影子对象只有需要的时候才会生成。如上图所示在JavaScript解释器看来它们之间的关系只有语法关系。例如documentAppendChild(node)document对象和node对象处于同一个语法树中它们之间只有这层关系而它们在DOM树中的关系丢失了想要使用这个关系需要绕道DOM或者利用ScriptIterpreter提供的缓存(见)。图描述了整个过程其中调用DOM类的函数的过程绕了一个弯:用了一个callRuntimeMethod函数去作为访问该DOM类的函数的统一入口通过访问JSXXX类的一个静态表sinfo来找到对应的DOM类成员函数。这个是实现细节总体上看可以认为就是在属性表里查找。DOMDOMDOMDOM触发JavaScriptJavaScriptJavaScriptJavaScript在DOM中触发JavaScript是通过事件监听器(EventListener)来实现的。其基本结构是:DOM中使用EventListener接口每个支持事件的元素都实现了添加删除EventListener。监听器都是JavaScript的代码如何在DOM中访问呢?前面提到在解析网页过程中会对JavaScript源代码建立语法树监听器对应着语法树的某个子树因此只有把这个子树的根节点(应该是一个FunctionImp对象)的地址注册到对应的DOM节点中就行了。如图所示。图:JavaScript访问DOM的流程JavaScriptJavaScriptJavaScriptJavaScript执行环境的初始化DOM中在执行JavaScript语句之前创建一个JSDOMWindow对象这是和DOM中DOMWindow对象向对应的JSObject这是从DOM进入JavaScript解释器的入口。创建过程做两件事:一是初始化大量的基本类型的原型(prototype)即前面提到的原型链机制二是创建了一个全局JavaScript对象JSDOMWindow从而可以使得解释器能从JavaScript进入到DOM。情景举例以下通过两个简单的例子来看看WebKit中DOM和JavaScript互操作的方式。cookiecookiecookiecookie操作对在Web程序中设置cookie有两种方式一是利用httpequiv来指示浏览器设置另一种是由JavaScript对documentcookie赋值来实现。我们知道cookie的设置是由平台提供的API而不是由浏览器直接操作的这是出于安全的考虑另外显然JavaScript解释器本身是不会去调用这种api的。在WebKit由Document类实现cookie的操作。首先对httpequiv域进行解析其值为“setcookie”时调用Document::setCookies()函数并把Content域的内容传进去。经过解析之后调用平台相关代码例如Qt平台上就是用QNetworkCookieJar::setCookiesFromUrl或者QCookieJar::cookieJar()>setCookies这取决于Qt的版本。根据前面的阐述JavaScript会利用JSDocument类的prop所指向的属性表来关联documentcookie=xxx这个表达式和Document::setCookies函数。首先documentcookie=xxx经过语法解释之后会调用语法节点AssignDotNode对象的evaluate函数。这个函数先对等号右边求值得到一个字符串然后对左边的对象document(是JSDocument实例)调用其put函数。这里和通常的理解不同:等号左边不是“”后的cookie而是其所属的对象即document。进入到JSDocument::put函数(继承自JSObject::put)后控制权就到了JSDocument类的内部了。这个函数是往prop指向的属性表添加或更新条目的其中会对每个条目进行区分对待如果是变量则设置之如果是函数则调用之。在JSDocument中cookie对应的是图:(左图)事件监听器的类结构(右图)监听器的添加过程如下的一个条目:对应了两个函数一个是getter一个是setter根据具体的上下文来确定调用哪个。到这里就进入全局函数setJSDocumentCookie它从传入的JSDocument对象获取对应的Document对象的指针进而调用前面提到的Document::setCookies函数。获取cookie的方式与这个相似。图展示了整个过程及所涉及到的类。JavaScriptJavaScriptJavaScriptJavaScript对象生存期控制前面提到JavaScript解释器对待每个对象都是一样的:只要你有proto和prop两个变量对对象每个操作都归结为查找prop指向的属性表及proto指向原型链上的某对象的属性表。这是一种通用的模式在这个模式下可以很容易的扩展解释器。但是对于对象的销毁没有提供接口。由于JavaScript语言规范没有给出对象生存期的规定因此JavaScript解释器只提供创建对象的接口对象的销毁时机取决于该对象是否还被其他对象使用也就是引用计数机制但是JavaScript解释器并不是利用常规的引用计数来实现的它使用了两种机制:一个是protect集合和栈对象集合一个是mark。JavaScript解释器每次在new一个对象是就会收集一次垃圾:遍历所有的对象把那些不用的对象销毁掉在遍历之前如果某个对象在protect集合或者栈对象集合中则对其进行mark操作。对一个对象进行mark操作就是对它的一个标志位置位告诉解释器不要把这个对象销毁。Protect集合一般用于那些全局对象它们没有被其他对象引用为了让它们在垃圾收集前被mark必须显式对其进行protect即添加到protect集合中。而栈对象集合则是当前JavaScript代码的局部域中的对象的集合它们也不会被其他对象引用但是肯定不应该被销毁。对于DOM对象在JavaScript中的影子对象binding代码中提供了另一种机制来辅助mark。通过一个ScriptInterpreter类维护两个表如图所示。右上侧是一个二级结构:首先是由Document对象做索引其指向的二级表中是该Document中的DOM对象和影子对象的对应表。左下角的表则用于存储那些不属于某个Document的对象如window。{"cookie",DontDelete,(intptrt)jsDocumentCookie,(intptrt)setJSDocumentCookie}图:WebKit对cookie的操作过程有了这两个表在mark某个对象时就可以把这个Document中已经建立的所有影子对象都mark一下。总结执行JavaScript是现代浏览器的重要功能是浏览器作为互联网服务的客户端平台的基础。WebKit对JavaScript解释器的实现代码比较清晰与DOM之间的binding耦合度相比于Firefox要高但是结构更简单。了解WebKit宿纳JavaScript的细节有助于理解基于WebKit开发的模式。本文是在阅读WebKit和WebKitr的代码的基础上完成的(DOM的代码主要是看的WebKitrJavaScript解释器代码主要是看的WebKitr)版本比较老了尤其是JavaScript解释器的代码改动非常大例如现在版本已经引入了JIT这里只是希望能提供一个关于WebKit中JavaScript解释过程的大致框架带着框架阅读代码效果会好些。由于代码量大文中还有很多不对的地方欢迎指正。参考资料:WC的DOM标准文档:http:wwwworgDOMDOMTRWebKit官方网站:http:webkitorg图:文档内JavaScript对象和DOM对象对应表及全局对应表WebKit的JavaScript引擎简介基于WebKitr概述浏览器脚本语言DOMJavaScript解释器解释器眼中的JavaScript对象JavaScript语法树宿纳JavaScriptJavaScript操作DOMDOM触发JavaScriptJavaScript执行环境的初始化情景举例cookie操作JavaScript对象生存期控制总结

精彩专题

热门资料

我整整读了5遍,每一遍都让我感觉又前进了一步1.pdf

24孝图1.pdf

你留意过自己的父母吗?1.pdf

励志文摘1.pdf

该用户的其他资料

  • 名称/格式
  • 评分
  • 下载次数
  • 资料大小
  • 上传时间

用户评论

0/200
    暂无评论
上传我的资料

相关资料换一换

资料评价:

/ 9
所需积分:0 立即下载
返回
顶部
举报
资料
关闭

温馨提示

感谢您对爱问共享资料的支持,精彩活动将尽快为您呈现,敬请期待!