浏览器工作机制

来源:互联网 发布:淘宝评价好评怎么修改 编辑:程序博客网 时间:2024/06/07 21:32

html及相关文档加载/解析/执行顺序

浏览器的5个常驻线程: 

浏览器GUI渲染线程 

javascript引擎线程 

浏览器定时器触发线程(setTimeout) 

浏览器事件触发线程 

浏览器http异步请求线程(.jpg <link />这类请求图片/视频资源 css


 

  1. HTML文档加载步骤:

HTML DOM文档加载是从上往下的按顺序执行的,一般浏览器的加载步骤是:

  1. 解析HTML结构和代码。
  2. 加载外部脚本和样式表文件(即外链的.js和.css等)。
  3. 解析并执行脚本代码。
  4. 构造HTML DOM模型。
  5. 加载图片、flash等外部文件。
  6. 页面加载完毕。
  7. AJAX异步请求。


渲染的主要步骤 点击打开链接


渲染引擎首先通过网络部分获取request响应的文档内容,一般以8K为单位分块进行。解析HTML用以构建DOM树结构 -> 构建Render树 -> Render树的布局 -> 绘制Render树。


渲染引擎解释HTML文档,首先将标签转换成DOM树中的DOM node;接着,解释对应的CSS样式文件信息,而这些样式信息以及HTML中可见的指令(<b></b>etc)将被用来构建另外的Render树。这棵树主要由一些包含颜色和宽高等属性组成的矩形组成,它们将依次按顺序显示在屏幕上。


待Renter树构建完成,将执行布局过程,确定每个节点在屏幕上对应的坐标,及其覆盖和回流情况等,然后浏览器会遍历Renter树,并使用UI后端层绘制每个节点。这整个过程都是逐步进行的,HTML内容显示一部分,将构建和布局一部分Renter树,就先显示出来。也就是解释完一部分内容就显示一部分出来,而不是等着HTML内容解释完成才进行构建Renter树,同时,还有可能通过网络层进行下载其余的内容。





脚本处理和CSS顺序

web的模式是同步的,解析到一个script标签立即解析执行脚本,并阻塞文档的解析直到脚本执行完成如果脚本是外引的,则网络必须先请求到这个资源 —— 这个过程也是同步的,阻塞文档的解析直到资源被请求到。不过,开发者可以将脚本标示为defer,以使其不阻塞文档的正常解析,并在文档的解析完成后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行可以使用另一个线程。


预解析

Chrome和Firefox进行了解析的优化,当执行脚本时,另一个线程解析剩余的文档,并加载后面需要通过网络加载的资源。这种方式可以使用资源并行加载,从而使整体速度加快。但是,预解析并不改变DOM树,它只是解析外部资源的引用,例如:外部脚本、样式表、图片等。


理论上,样式表的解析并不改变DOM树,但是脚本可能在文档的解析过程中请求样式信息,假如样式信息还没有加载和解析,脚本将得到错误的信息,显然这会导致很多问题。Chrome只在当脚本试图访问可能被未加载的样式表所影响的特定样式属性时才会阻塞脚本。


渲染树的构建

当DOM树构建完成,又可是构建另一棵 —— 渲染树。渲染树由元素显示序列中的可见元素组成,是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。

渲染树和DOM树的关系

渲染对象和DOM元素是相对应,但这种关系关系不是一对一的,不可见的DOM元素不会被插入渲染树之中。例如:display为none,但是visibility为hidden的元素却将出现在渲染树种,因为它需要渲染,只是不显示在屏幕上而已。

另外还有一些DOM元素对应几个可见的对象,例如:select元素,它是个相对复杂结构的元素,由三个渲染对象组成:显示区域、下拉列表、按钮。同样,当文本输入因为宽度不够而折行时,新增的行将作为额外的渲染元素被添加。

还有一些渲染对象和应当对应的DOM节点不在两棵树相同的位置,例如:浮动和绝对定位的元素在正常流之外,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。




关于浏览器js引擎线程和setTimeOut线程 来自从setTimeout到浏览器线程机制

看高性能javascipt 这本书时,看到这么一句话:

Putting scripts at the top of the page in this way typically leads to a noticeable delay, often in the form of a blank white page, before the user can even begin reading or otherwise interacting with the page.  

解释如下:

将script脚本放在头部将导致一个明显的延迟,通常的表现为:页面打开时一片空白,用户不能阅读也不能有任何交互。

我的理解是:将js放在头部,js的加载和执行导致页面渲染推迟。如果js放在尾部,由于页面渲染先于js的加载或执行,可以优先呈现给用户而不受js的阻塞影响。

为了验证,做了以下实验:

<p>hello world</p>
<script type="text/JavaScript">
  function f() {
    var t = +new Date();

    //运行5秒
    while(true) {
      if(+new Date() - t > 5000) {
        break;
      }
    }
  }
  f();  // ①
</script>

实验结果有意思了:hello world 等了5秒后才出来。(亲自验证sfr/gg/ff,效果相同,等5秒)  

( 注意:js加载执行完,整个html文件资源才会在调试器呈现,在想可不可以理解成,此时html文档才算是加载完毕???

另外,关于jQuery,$(document).ready(), 意思是在html文档结构加载完成时调用,。。。

深度验证:将以上script里面部分作为js外链引入或是动态加载。结果依然是等待了5秒后才出来。

也就意味着页面的呈现必须是在所有js加载并执行完成后才会出现。上面那段话是有问题的。

这是否就意味着只要js不涉及到dom操作,放在头部和尾部是同样效果呢?当然不是。例如当html里面有图片或其他资源的时候,如果将js放在头部,图片的下载需要等待js运行完之后才开始,而如果js放入尾部,由于图片下载并不阻塞js的运行,可实现图片的下载和js运行的并行。

 

问题到此,是否就结束了呢?当然没有,前端工作很重要的一部分是性能优化,针对上述的js,如何进行优化呢?于是想到了setTimeout。

setTimeout 用处在于延迟执行js代码,它有个好处就是不会阻塞它后面js的执行。于是猜想setTimeout同样不会阻塞页面的渲染。

方案一:将上面的①部分替换为: setTimeout(f, 0); 

结果hello world 还是要等待5秒后才出来。(亲自验证sfr/gg立马出结果,ff等待5秒。但是如果细心的话你会发现浏览器标签上的load符号不见了。

继续猜想,0太小了,导致浏览器发现 setTimeout 后面没有js代码后马上就执行了setTimeout 里面的内容,即 f 函数。于是把时间改成100ms。

方案二:将上面的①部分替换为:setTimeout(f, 100);

结果hello world瞬间弹出。(亲自验证sfr/gg/ff,效果相同,全是瞬间。有点小激动。有兴趣的同学可以继续测,这时你会发现会有一个临界值(不同浏览器的临界值不同),当setTimeout 第二个参数大于这个临界值时,hello world会瞬间弹出,反之则需要等待里面函数运行完成后弹出。

 

太神奇了,为什么会出现这种情况?要解答这个问题,我们必须要研究一下浏览器的线程机制了。

我们知道浏览器内部至少会有这么两个线程:解析js的线程和渲染界面的线程。这里我们暂且称它们为JS线程和UI线程。

由于js是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器控制JS线程和UI线程以列队的形式同步执行

回到上面的问题,setTimeout执行时会新开一个定时器线程,这是正好处于JS线程运行当中,当JS线程执行完成后,发现setTimeout马上就要开始执行(即时间小于上述的临界值),为了避免UI线程运行时间太长而带来的setTimeout严重拖时的不好体验,浏览器选择一直等待直到setTimeout到期,然后运行里面的js。如果发现setTimeout还要隔较长时间才到期,为了避免时间上的浪费,浏览器选择马上切换到UI线程。 

 

结论:setTimeout 可用于处理耗时的js代码,但千万要小心第二个参数不要设置太小了,否则你看到的同样是一个空白页面。推荐在100ms左右,可满足所有浏览器。当然,如果可以不兼容IE的话,抛弃setTimeout吧,web workers 会是个很好的选择。



浏览器渲染页面和解析加载页面机制。

来自浏览器页面加载解析渲染机制

a. 加载 ,即为获取资源文件的过程,不同浏览器,以及他们的不同版本在实现这一过程时,会有不同的实现效果(资源间互相阻塞,可以用timeline来做测试)。这里先说下浏览器的5个常驻线程: 

浏览器GUI渲染线程 

javascript引擎线程 

浏览器定时器触发线程(setTimeout) 

浏览器事件触发线程 

浏览器http异步请求线程(.jpg <link />这类请求) 

备注: 现代浏览器存在 prefetch 优化,浏览器会另外开启线程,提前下载js、css文件,需要注意的是,预加载js并不会改变dom结构,他将这个工作留给主加载。 

注意: 这里也涉及到 阻塞 的现象,js引擎线程(第二个)进行时,会挂起其他一切线程,这个时候3、4、5这三类线程也会产生不同的异步事件,由于 javascript引擎线程为单线程,所以代码都是先压到队列,采用先进先出的方式运行,事件处理函数timer函数也会压在队列中,不断的从队头取出事件,这就叫:javascript-event-loop。简单点说应该是当在进行第二线程的时候,1,3,4,5都会挂起,比如这时候触发click事件,即使先前JS已经加载完成,click事件会压在队列里,这里也要先完成第二线程才会执行click事件。 

实践(xrr)

<p>hello world.,..,,.</p>
<button id="btn">button</button><script type="text/JavaScript">window.onload = function() {   document.getElementById("btn").addEventListener('click',function () {
         alert("abc");//断点    });  function f() {    var t = +new Date();    //运行5秒    while(true) {      if(+new Date() - t > 6000) {        break;//断点      }    }  }  f();  // ①};</script>

断点发现 执行到break之前 点击btn无效 知道执行到break之后 点击btn才有效

加载顺序: 浏览器解析http response 下载html文件会”自上而下“加载,并在加载过程中进行解析渲染。“自上而下”加载时遇到图片、视频之类资源时便会进入第5个线程,这是异步请求,并不会影响html文档进行加载。 加载过程中遇到外部css文件,浏览器另外发出一个请求,来获取css文件。这里也是第5个线程,这里css解析会生成一个rule tree(规则树),这个以后会更新。 当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。 

原因:JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。

办法:可以将外部引用的js文件放在</body>前。

4. css可能影响js的执行造成阻塞。 

原因:如js里面var width = $('#id').width();这里js执行前,浏览器必须保证之前的css文件已下载和解析完成(后面的不会影响),这也是css阻塞后续js的根本原因。当js文件不需要依赖css文件时,可以将js文件放在头部css的前面。

5.预加载网页,利用空余时间来提前加载该网页的后续网页。

<link rel="prefetch" href="http://"> 

6.为js脚本添加defer属性,使得浏览器不等js脚本加载执行完,就加载后面的图片。既然图片资源都已经加载出来了,就不要在js内写document.write啦。

<script defer="true" src="JavaScript.js" type="text/javascript"/> 

先写这么多吧,脑容量告急!这里面大部分都经过本人测试。后续还会写下对解析和渲染的理解。


0


0 0
原创粉丝点击