大多数设备的刷新频率是60Hz,也就说是浏览器对每一帧画面的渲染工作要在16ms内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验。前端的用户体验给了前端直观的印象,因此对B/S架构的开发人员来说,熟悉浏览器的内部执行原理显得尤为重要。

 

浏览器主要组成与浏览器线程

1.1 浏览器组件

浏览器大体上由以下几个组件组成,各个浏览器可能有一点不同。

浏览器渲染基本原理(一):浏览器主要组成与浏览器线程_渲染引擎

界面控件 – 包括地址栏,前进后退,书签菜单等窗口上除了网页显示区域以外的部分

浏览器引擎 – 查询与操作渲染引擎的接口

渲染引擎 – 负责显示请求的内容。比如请求到HTML, 它会负责解析HTML、CSS并将结果显示到窗口中

网络 – 用于网络请求, 如HTTP请求。它包括平台无关的接口和各平台独立的实现

UI后端 – 绘制基础元件,如组合框与窗口。它提供平台无关的接口,内部使用操作系统的相应实现

JS解释器/js引擎--- - 用于解析执行JavaScript代码

数据存储持久层 - 浏览器需要把所有数据存到硬盘上,如cookies。新的HTML5规范规定了一个完整(虽然轻量级)的浏览器中的数据库 web database

浏览器的核心是两部分:渲染引擎和JavaScript解释器(又称JavaScript引擎)。最开始渲染引擎和js引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。

javascript在浏览器的实现中还必须含有DOM和BOM。Web浏览器一般使用公共 API来创建主机对象来负责将DOM对象反射进JavaScript。JS引擎负责对JavaScript进行解释、编译和执行,以使网页达到一些动态的效果。

BOM是浏览器对象模型,用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等。

DOM是文档对象模型,用来获取或设置文档中标签的属性,例如获取或者设置input表单的value值。我们知道HTML是由标签组成的,标签套标签。JavaScript可以通过DOM获取到底有哪些标签,标签里面的属性是什么,内容是什么等等…

渲染引擎

渲染引擎的主要作用是,将网页从代码“渲染”为用户视觉上可以感知的平面文档。不同的浏览器有不同的渲染引擎。

Firefox: Gecko引擎
Safari:   WebKit引擎
Chrome:Blink引擎
IE:  Trident引擎
Edge:  EdgeHTML引擎

渲染引擎处理网页,通常分成四个阶段。

解析代码:HTML代码解析为DOM,CSS代码解析为CSSOM(CSS Object Model)
对象合成:将DOM和CSSOM合成一棵渲染树(render tree)
布局:计算出渲染树的布局(layout)
绘制:将渲染树绘制到屏幕
以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的HTML代码还没下载完,但浏览器已经显示出内容了。

JavaScript引擎

JavaScript引擎的主要作用是,读取网页中的JavaScript代码,对其处理后运行。

上节主要介绍JavaScript引擎的工作方式

 

 注意:chrome浏览器与其他浏览器不同,chrome使用多个渲染引擎实例每个Tab页一个,即每个Tab都是一个独立进程

 重点再强调下,javascript是单线程运行,千万别被setTimeout()和setInterVal()这种函数迷惑而误以为它是多线程。

    一般而言,<script>标签每次出现都会霸道地让页面等待脚本的解析和执行,无论当前的Javascript是内嵌的还是包含了外链文件,页面的下载和渲染都必须停下来等待js脚本执行完成这在页面的生存周期中是必要的,因为脚本执行过程中可能修改页面内容,一个典型的例子就是在页面中使用document.write()。

     当javascript代码是内嵌在html里面时,这点还是比较容易理解,但当javascript是外链文件时稍微有点负载,因为存在一个加载过程,而且浏览器加载好这个js文件之后往往还对其缓存

     首先,我们用以下这个例子来说明下缓存问题

<html>
<head> 
  <script type='text/javascript' src='js/f1.js'></script>
</head>
<body>
</body>
</html>

浏览器渲染基本原理(一):浏览器主要组成与浏览器线程_渲染引擎_02

    从上例中可以明显看出,像chrome之类的高版本浏览器会对js文件进行缓存,作用是不言而喻,减少网络请求

 

 

1.2 浏览器中的进程与线程

Chrome浏览器使用多个进程来隔离不同的网页,在Chrome中打开一个网页相当于起了一个进程每个tab网页都有由其独立的渲染引擎实例因为如果不是多进程的话,如果浏览器中的一个tab网页崩溃,将会导致其他被打开的网页应用另外相对于线程,进程之间是不共享资源和地址空间的所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

1. GUI 渲染线程

GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态,也就是说被冻结了。

2. JavaScript引擎线程

JS为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JS是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突;如果JS是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果,当然我们可以通过来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,JS在最初就选择了单线程执行。

GUI渲染线程与JS引擎线程互斥的,是由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。由于GUI渲染线程与JS执行线程是互斥的关系,当浏览器在执行JS程序的时候,GUI渲染线程会被保存在一个队列中,直到JS程序执行完成,才会接着执行。因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

3. 定时触发器线程

浏览器定时计数器并不是由JS引擎计数的, 因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

4. 事件触发线程

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

5. 异步http请求线程

在XMLHttpRequest在连接后是通过浏览器新开一个线程请求,将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到JS引擎的处理队列中等待处理。