这篇文章主要是两个方面:浏览器内核工作原理的简要分析(以WebKit2为例)。浅析浏览器内核想到的前端优化,或者说前端优化规则从何而来。众所周知,大部分WEB页面都是由浏览器渲染的,而浏览器能否显示页面基本上取决于浏览器的内核,即渲染引擎。今天以Chrome浏览器的核心WebKit(更准确地说是WebKit分支Blink,以下统称WebKit)为例,简单全面的了解一下渲染引擎是如何展示页面的。浏览器的渲染引擎及其依赖模块主要处理HTML、CSS、图片、JavaScript等网页资源,最终呈现显示的图像。渲染引擎主要包括解析这些资源的处理器,如HTML解释器、CSS解释器、布局计算+绘图工具、JavaScript引擎等。为了更好地渲染渲染效果,渲染引擎还依赖于网络栈、缓存机制、绘图工具、硬件加速机制等。浏览器的渲染过程浏览器的渲染过程主要包括两部分:网页资源加载过程和渲染过程。上图大致分析了整个网页渲染过程。下面我们按照数据流向,对每个过程进行一一详细分析。1、域名解析DNS当我们在浏览器中输入网址时,浏览器会先进行域名解析。一般情况下,一次DNS域名解析大约需要60-120ms,一次TCP三次握手需要1.5RTT(往返时间)。WebKit的解决方案是使用DNS预取技术和TCP预连接技术。DNS预取技术利用现有的DNS机制,提前解析网页中可能存在的网络连接。即使用较少的CPU和网络带宽来解析用户浏览的网页中链接的域名或IP地址;当用户点击链接时,会节省时间~尤其是域名解析慢的时候~同样,当在地址栏输入链接时,候选者也会默默的进行DNS预取~。在DNS预取之后,TCP连接被预先建立。前端优化建议:页面指定预取域名:大数据分析,猜出链接用户可以点击,提前预取。减少页面上的域名数量可以直接减少DNS请求。2、SPDY与HTTP2由于请求导致的TCP三次握手的1.5RTT延迟,Google引入SPDY试图解决HTTP(HTTP明文模式)的延迟和安全问题。但SPDY推动HTTP2.0诞生后,就不再自我更新,逐渐退出。SPDY基于SSL,可轻松兼容新旧版本的HTTP。其优点如下:多路复用。一个TCP连接传输多个资源。降低TCP连接成本。不同的资源,不同的优先级。例如,首先加载第一个屏幕。标题压缩。减少传输的字节数。SPDY最多可以压缩Header80%。SPDY为HTTP开辟了新局面,扼杀了我们过多的前端优化工作,从本质上提升了页面加载速度。但是我们的前端优化工作还是不能怠慢。在继续减少请求和减少TCP连接建立的路上,我们继续。合并资源,如combo合并JavaScript文件和CSS文件,使用sprite合并图片、图像贴图等;当页面资源较小时,可以直接放在页面上,比如小图片可以使用Base64编码格式导入。甚至一些基础样式,或者首屏依赖样式,都可以放在页面上;资源压缩技术。比如Gzip等。主要是响应数据的压缩~精简JavaScript和CSS代码。减少无用的空格。压缩混乱~避免链接重定向,避免错误的链接请求。建立多条链路和多条DNS解析,阻碍DNS预取技术。及时更新你页面中没有价值的链接。3.资源加载域名解析完成,TCP连接建立后,资源加载器开始工作。资源和资源加载器资源包括:HTML、JavaScript、CSS样式表、图片、SVG、字体文件、视频和音频等。资源加载器分为三种:特定加载器,只加载一种。比如ImageLoader类。缓存机制的资源加载器。具体加载器用它来查找是否有缓存资源,属于HTML的文档对象。通用资源加载器。当WebKit需要从网络或文件系统中获取资源时使用。只负责获取资源数据,由所有特定的资源加载器共享。资源加载的过程在WebKit中,所有的资源都是以CachedResource为基础,并以Cached为前缀,体现了浏览器的缓存机制。即在请求资源时,浏览器会先检查缓存中是否存在该资源,然后再决定是否向服务器发送请求。这会导致两个问题,第一,缓存资源的生命周期。浏览器缓存不会永远增加,缓存池中的数据必然会被替换。WebKit使用LRU最近最少使用算法来更新缓存池数据。WebKit遵循HTTP协议。页面刷新时判断资源是否在资源池中。如果存在,则附加该资源的一些本地信息(如修改时间等),向服务器发送HTTP请求,服务器根据这些信息进行判断。如果资源没有更新,网络状态为304,使用现有资源;否则,执行资源加载过程。二、资源加载过程。当资源池中不存在该资源时,执行加载过程。WebKit可以并行(多线程)下载普通资源和JavaScript资源。当当前主线程阻塞时,WebKit会启动另一个线程遍历后面的网页,收集需要的资源URL并发送请求,避免阻塞。基于资源加载,前端优化建议:使用缓存机制缓存常用的、短时间内不会变化的资源,或者为资源设置过期时间。例如,设置ETag/Last-Modified和Expires/Cache-Control。Expires/Cache-Control两者功能相同,表示资源的有效期。如果本地缓存还在有效期内,浏览器会直接使用本地缓存,不再发送请求。两者同时配置时,Cache-Control高于Expires。配置ETag/Last-Modified后,浏览器再次访问该URL时,也会向服务器发送请求,确认文件是否被修改。如果不修改,服务器返回304,浏览器直接从本地缓存中获取数据;如果修改了,服务器会将数据返回给浏览器。如果两者同时配置,服务器会优先检测ETag,如果一致则继续检测Last-Modified。同时配置两者可以让服务器更准确地判断浏览器是否已经有需要的缓存数据。当同时设置了ETag/Last-Modified和Expires/Cache-Control时,Expires/Cache-Control具有更高的优先级。因此,只要本地缓存有效,就不会发送任何请求。但是当页面F5刷新或者强制刷新时,缓存就会失效。鉴于资源下载可能出现阻塞,将JavaScript文件放在页面底部。javascript资源是阻塞主线程的,重建一个线程需要时间,所以把javascript扔掉~但是javascript资源不影响前面资源的加载和DOM树的构建。4、从URL到DOM树的构建当我们获取到页面需要的资源后,渲染引擎启动HTML解释器对获取到的资源进行解析和处理。网页代码(字节流)经过词法分析器解码,再由词法分析器解释成单词Token,再构造成节点Node,最终构造出DOM树。期间,当节点为JavaScript节点时,会启动JavaScript引擎,会阻塞DOM树的构建。因为JavaScript很可能在JavaScript执行过程中对DOM树进行读写。在JavaScript执行完毕之前,DOM树不会恢复构建。其他资源不影响DOM树的构建。在前端优化中,建议将CSS文件放在页面顶部,构建DOM树;并尽量将JavaScript文件放在页面底部,防止阻塞DOM树的构建;并且在JavaScript的onload事件中,不要写太多影响首屏渲染的操作DOM树的JavaScript代码。另外强调一下:DOMContentLoaded:DOM树构建;DOMonload事件:DOM树构建完毕,网页所依赖的资源全部加载完毕~5.网页布局过程:从DOM树到构建RenderLayer树的过程就像一个页面排版过程。它通过CSS样式信息对DOM树进行排版,形成RenderObject树和RenderLayer树。在构建DOM树之后,WebKit为DOM树节点构建RenderObject对象。WebKit会根据盒子模型计算节点的位置、大小等样式信息(即布局计算或排版),并将这些信息保存到对应的RenderObject对象中。1.CSSInterpreterCSS解释过程就是一个CSS字符串经过CSS解释器(CSSParser、CSSGrammer)的处理,成为渲染引擎内部样式规则表示的过程。风格规则是解释器的输出结构和风格匹配的输入数据。具体过程:WebKit渲染元素时,CSS解释器获取样式信息,返回匹配结果样式信息。每个元素可能需要匹配来自不同来源的规则,这些规则依次是用户代理(浏览器)规则集、用户规则集和HTML页面中包含的自定义规则集。三者的搭配方法大同小异。对于每个规则集,首先查找ID规则,检查是否匹配,然后检查类型规则、标签规则等。匹配规则保存到匹配结果中。WebKit对这些规则进行排序。对于元素需要的style属性,WebKit从高优先级的规则中选择并返回style属性值。2.渲染基础:RenderObject树DOM树经过布局计算和CSS解析后,样式信息存储在RenderObject对象中,构建成RenderObject树。同时WebKit会根据网页的层级结构创建RenderLayer树来完成绘制上下文。DOM树、渲染树和绘图上下文共存,直到页面被销毁。RenderObject树是一种基于DOM树的新树,是布局计算和渲染机制的基础设施。当一个DOM节点创建一个新的RenderObject对象时:DOM树的文档节点。DOM树的可视节点,如html、body、div等。不创建非可视节点,如meta、head、script等。为了满足WebKit处理,需要建立一个匿名的RenderObject节点,不对应DOM树的任何节点。例如:匿名RenderBlock节点。DOM树的每个节点对象都会递归检查是否需要创建RenderObject,根据DOM节点类型创建RenderObject节点;动态添加的DOM元素将相应地创建一个RenderObject节点。所有这些节点形成一个RenderObject树。3、渲染基础:在HTML页面上展示网页层次结构和RenderLayer树,网页分层展示。目的有二:1、方便网页的开发,设置网页的层级;2.简化WebKit渲染逻辑。WebKit在RenderObject树的基础上,根据需要为其中的一些创建新的RenderLayer节点,形成一棵RenderLayer树。RenderObject节点创建新的RenderLayer对象时:DOM树的Document节点对应的RenderView节点。DOM树中Document的子节点,即HTML节点对应的RenderBlock节点。明确指定CSS位置的RenderObject节点。具有透明效果的RenderObject节点。具有溢出、alpha或反射等效果的节点RenderObject节点。RenderObject节点使用Canvas2D、3D(WebGL)技术。Video节点对应的RenderObject节点。使用RenderLayer节点可以有效降低网页结构的复杂度,在很多情况下可以减少重新渲染的开销。4、布局计算和重绘时机CSS盒模型是布局计算的基础;渲染引擎用来决定如何布局元素以及元素之间的位置关系。布局计算是对RenderObject树及其子树的计算。这是一个递归计算。节点信息需要先计算其子节点的位置和大小。RenderObject对象会存储计算结果并等待渲染机会。每个元素都实现自己的布局。如果页面元素定义了宽度和高度,则元素大小根据自定义宽度和高度确定。对于文本节点等行内元素,需要结合字体大小和文本量来确定宽高。页面元素决定的宽高超过布局容器的包含块提供的宽高,overflow是visible或者auto,WebKit提供滚动条保证所有内容都能显示。一般页面元素的宽高是在布局时计算出来的。除非网页定义了页面元素的宽度和高度。重绘时间:每当样式改变时重新计算。***打开页面,浏览器设置网页可见区域,并调用计算布局的方法。当可见区域发生变化时,网页包含块的大小也会发生变化,WebKit需要重新计算布局。网页动画触发布局计算。动画可能会改变样式属性。JavaScript通过CSSOM(CSSObjectModel)直接修改样式,会触发WebKit重新计算布局。用户交互,例如滚动网页。前端优化建议,由于布局计算需要时间,一旦布局发生变化,WebKit后期需要重绘。SO,减少样式变化~减少重绘~使用CSS3新功能(如CSS3变形translate、scale、rotate等方法、transition方法等)可以有效提高网页的渲染效率。6、网页渲染过程:从RenderLayer树到最终图片在前面的过程中,网页完成了从DOM树到RenderLayer树的布局计算和排版处理。接下来由渲染引擎(通常是绘图工具)完成RenderLayer树的绘制,最终形成图像展示给用户。1.Drawingcontext绘图上下文,所有的绘图操作都是在这个上下文中进行的。它是一个独立于平台的抽象类,将每个绘图操作桥接到不同的绘图具体实现类。二维绘图上下文:提供基本绘图单元的绘图界面,设置绘图风格。绘图界面包括:绘图点、绘图线、绘图图片、绘图多边形、绘图文字等。绘图样式包括颜色、线宽、字体大小、渐变等。CPU进行2D运算。或者使用3D图形接口(OpenGL)完成。3D绘图上下文:支持CSS3D、WebGL等。使用3D图形接口(OpenGL、Direct3D等)2.渲染方式软件渲染:CPU。通常渲染的结果是一个位图,在绘制每一层的时候用到,不同的是位置可能不同,每一层都是按照从后到前的顺序。无需为每一层分配位图,无需合成。缺点:对HTML5新技术、CSS3D、WebGL能力不足;性能不佳,如视频、Canvas2D;使用率下降,尤其是在移动终端上。优点:对于更新区域处理,软件渲染可能只需要计算很小的区域,而硬件需要绘制一层或多层,然后再合成。硬件很贵。硬件加速渲染:GPU必须有一个合成步骤。分层绘图+合成。但是对于更新区域,如果只是在一层,硬件可能会更快。WebKit如何做到:使用适当的网页分层技术,减少重新计算的布局和绘制。使用CSS3D变形和动画技术。CSS3D变形技术允许浏览器通过使用合成器合成所有图层来实现动画效果。不需要布局计算和重绘~前端优化建议:减少重绘:因为重绘是分三个阶段计算布局、绘制、合成。其中layout和drawing的计算比较耗时,综合比较少。7、小结至此,我们已经大致介绍了从输入URL到页面渲染。但这只是表面的顶部。更多浏览器内核的精华,值得下载一份源码,编译、分析、挖掘~相信在前端优化的道路上,知其然,知其所以然~才会走的更远~~8.参考资料朱永生《WebKit技术内幕》HTTP协议WebKit源码