前端知识点之浏览器相关

103 阅读10分钟

浏览器相关

1.浏览器渲染原理

首先,浏览器的网络进程通过http请求html,之后网络进程通过消息队列将数据传给渲染进程,之后渲染进程通过GUI渲染线程进行页面的渲染。页面的渲染主要有Dom树的构建,Cssom树的构建,渲染树的生成,回流,重绘以及分层和合成等几个阶段。

首先解析html生成Dom树,遇到css时解析css生成cssom树,dom树和cssom树的生成是通过栈来实现的。在这期间,如果遇到js,则停止树的解析执行js代码。为了增加浏览器的解析效率,浏览器存在预加载功能,预先加载将要使用的文件,减少网络等待的时间。之后,将可见部分的dom节点结合dom树和cssom树生成rander渲染树。渲染树中包括每个所要绘制的节点及其对应的样式(包括继承而来的样式和自身的样式)。之后在回流阶段计算每个元素所占的大小以及位置,最后是重绘阶段对渲染树绘制到屏幕上,这个阶段也叫做光栅化阶段,及生成位图。

为了增加绘制的效率,常常对所要绘制的元素进行分层,比如video,canvas等被分到不同的层进行绘制,在绘制完成后再进行分层的合成。

2.浏览器的工作原理(当输入一个url到展示页面,浏览器会发生什么变化)

浏览器是一个多进程的,主要包括浏览器主进程,GPU进程,渲染进程,插件进程和网络进程。浏览器主进程主要是协调各个进程之间的通讯以及处理用户输入等。GPU进程主要是处理跟GPU有关的操作,比如3D渲染,视频处理等。插件进程是处理插件相关的进程,因为可能影响到其他进程,所以将其单独独立出一个进程。网络进程主要处理网络通讯相关。而比较重要的就是渲染进程,渲染进程中包括页面渲染GUI线程以及JavaScript引擎线程。而各个进程之间的通讯需要通过消息队列。

当用户输入一个url时,由于浏览器采用的TCP/IP协议,首先会向DNS域名解析系统发送请求,获取域名所对应的IP地址(查找路径:浏览器缓存 -> 本地电脑hosts文件 -> DNS服务器 -> 运营商)。紧接着,浏览器根据获取到的IP地址向对应的主机端口发送请求HTTP请求,首先进行三次握手,浏览器作文客户端首先先向服务器发送syn包,服务器收到后向浏览器发送syn+ack包表示收到了浏览器发出的请求,最后浏览器向服务器发送ack包告诉服务器我收到了你发送过来的确认包,这样三次连接建立,进行数据的传输。

当浏览器接受到服务器发送过来的HTML文件时,开始对文件进行解析,首先将二进制数据转化成为浏览器能够识别的文本数据。解析从上到下依次执行。首先解析HTML,为每个标签打上开始和结束的标志,采用入栈出栈的方式将HTML转为DOM树。如果遇到CSS(内联CSS和外部引用的CSS),将CSS解析成CSSOM树,使用的方式与解析HTML相似。当解析的过程中遇到JavaScript脚本时,因为浏览器会默认JavaScript脚本可能会修改页面的元素,则CSS和HTML的解析停止,开始运行JavaScript的脚本,这也是为什么要将script标签放到最下边的原因。但是现在有了新的方式,defer和async可以异步的加载脚本。defer是先加载脚本,等页面解析完毕后再运行脚本。async是等脚本异步加载完毕直接开始执行。

当CSSOM树和DOM树创建完毕后,针对页面中可见的元素,将CSSOM树和DOM树结合为Rander渲染树。渲染树中包括每个元素以及其对应的样式。包括继承而来的样式和自己本身的样式。

因为渲染树中没有包括每个元素具体的大小和位置,之后进行页面的重排,为每个要显示的元素计算其大小和位置。最后进行重绘,这个过程也叫做栅格化,即转化为位图。

为了提高渲染的效率,在重绘的过程中会进行分层。比如canvas,video都分别在单独的一层。在分层后还涉及到将每个层进行合并,最终渲染出最终的页面。

3.浏览器缓存

浏览器缓存也叫HTTP缓存,是通过HTTP的请求头实现的,可以将静态的资源文件html,css,js等保存在硬盘(1T)或者内存(8G/16G)中。根据是否需要重新向服务器发送请求,将浏览器缓存分为强缓存和协商缓存。

强缓存指不需要向服务器发送请求,直接使用缓存的结果,主要是通过http请求头中的expries和cache-control两个字段实现的,expries指资源的过期时间(但是是服务器的时间,与浏览器的时间可能不同从而影响命中缓存的结果),cache-control主要包含三个字段,分别是max-age:缓存的最大存活时间,其优先级大于expries,no-cache:协商缓存时使用,no-store:指是否开启浏览器缓存。当强缓存命中时,不会开启协商缓存。

当强缓存失效时(缓存时间过期&&no-cache),浏览器开启协商缓存。向服务器发送的请求头中包含last-modified:资源最后更改的时间(但是有个弊端,时间只能精确到秒),E-TAG:资源标识字符串,资源每发生依次变化,E-TAG就会发生变化。如果服务器的资源没有发生变化,则返回304以及Not Notified,告诉浏览器直接使用缓存。如果服务器的资源发生变化,则返回200状态码以及发生变化的资源。

缓存还分为memory cache:即将资源缓存在内存中,优点是读取速度快但是当tag页关闭的时候就会失效,当点击刷新按钮后,会看到好多资源使用了memory cache。disk cache:即将需要缓存的资源存储在硬盘中,容量大,但是读取的速度相对较慢。

4.垃圾回收机制

垃圾回收机制就是对程序运行过程中产生的垃圾进行回收的机制。因为在js中引用类型都是存放在堆中而在栈中存放了该引用类型在堆中的地址,所以当引用类型没有被其他变量引用时就会产生垃圾,这时就需要垃圾回收机制进行垃圾回收。像很多高级的语言都有垃圾回收机制,比如js,Java,python。但是像c,c++等就没有垃圾回收机制,就只能自己手动的去管理内存。

垃圾回收主要有两种算法,分别是标记清除法和引用计数法。

在标记清除算法中,主要分为两个阶段,分别是标记和清除。标记的方式有很多种,比如把一个二进制位置反或者维护一个进入作用域和出作用域的一个列表。标记清除算法的大致流程是这样:首先,假设内存中所有的对象都是垃圾,标记为零。其次,遍历内存中所有对象,对不是垃圾的对象标记为1。在清除阶段,清除内存中所有标记为0的对象,释放内存。最后,将所有的对象再全部置为0。标记清除算法只能隔一段之间对垃圾进行一次清除。实时的话开销太大了。还有一个弊端就是,再垃圾清除后,会产生碎片化的内存空间,影响较大数据的内存空间分配。

于是就产生了标记整理算法,即在进行标记后,把活动对象全部移到一边,把垃圾也就是非活动对象全部转移到内存的末尾。在进行清除时清除全部的非活动对象。

与标记清除算法不同,引用计数算法实现起来则比较简单,而且可以实时的进行垃圾回收。引用计数算法主要计算每个对象被引用的次数,如果被引用的次数变为0,则立即进行垃圾回收。但是这个算法有两个弊端,一个是需要维护比较大的一个计数变量,还有一个就是存在循环引用问题,这也是最为致命的一个问题。因此,现在的浏览器基本上用的都是标记清除算法。

V8引擎对垃圾回收所做的优化:

  1. 分代式垃圾回收:将内存中的垃圾分为新生代和老生代,对于新生代和老生代分别采用不同的垃圾回收机制。在新生代中,将堆内存一分为二,分别是使用区和空闲区。在使用区中进行垃圾的标记,当使用区满时,将所有使用区的数据复制到空闲区对非活动对象进行清除。此时,使用区和空闲区互换,经过多次互换后仍然存活下来的对象则进入老生代。在老生代堆内存中,采用标记清除算法对非活动对象进行垃圾回收。
  2. 并行垃圾回收:考虑到使用一个线程进行让垃圾回收的速度比较慢,可以多开几个线程进行垃圾回收操作。因为js是单线程的,这样也会使程序暂停下来进行垃圾回收,堵塞程序的运行,只不过现在堵塞的时间短了。在新生代的清除阶段,就是使用了并行垃圾回收的方式。因为老生代堆内存中都是比较大的数据,所以这种方式的作用并不大。
  3. 增量标记与惰性清理:因为在进行标记的过程中会阻塞js程序的运行,所以将标记阶段分为多个阶段进行标记,减少单次堵塞程序运行的时间,但是总的标记时间并没有改变,甚至还会有所增加(产生了新的垃圾,维护非活动对象)。惰性清理就是,在标记完成后,如果现在的内存空间够程序使用则可以不立马进行清理。在每次清理的过程中,也不是必须要将所有的垃圾进行清理。
  4. 并发垃圾回收:因为js在垃圾回收的过程中会影响js代码的执行,所以可以单开线程单独进行垃圾回收操作。(js引擎线程是在渲染进程中的)

此外,在老生代的垃圾回收机制中,综合运用了并行垃圾回收,并发垃圾回收以及增量标记和惰性清理的机制。在进行标记时使用并发的方式,在清除阶段则采用并行以及增量标记和惰性清理的方式进行清除。