浏览器渲染原理
1、JS的异步
js是单线程的语言,它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
渲染主线程承担着诸多工作,例如解析、渲染页面、执行js都在其中,如果使用同步会极有可能产生阻塞,从而导致消息队列中许多其他任务无法得到执行,造成1主线程白白的消耗时间 2页面无法及时更新,产生页面卡死现象。
所以浏览器采用异步的方式来解决,具体做法是(事件循环)当某些任务发生时,例如计时器、网络、事件监听,主线程将其交给其他线程处理,自身继续执行后续代码,当其他线程结束后,将事先传递的回调函数包装成任务加入到消息队列末尾,等待主线程的调度执行。
在这种异步模式下,浏览器永不阻塞,最大限度的保证了单线程的流畅运行。
2、事件循环
事件循环又称消息循环,是浏览器渲染主进程的工作方式。
在Chrome的源码中,它开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,其他线程只需要在合适的时候将任务加入到消息队列的末尾中即可。
早期把消息队列分为宏任务和微任务,这种说法已经无法满足复杂的浏览器环境,取而代之的是一种更灵活的方式,例如交互队列、延时队列、事件,根据w3c的解释,每个任务都有不同的类型,同类型的任务必须在同一个队列中,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级,在一次事件循环中,有浏览器自行决定取哪一个队列的任务,但必须有一个微队列,并且优先级最高。
js计时器能否精确计时?
不行,原因:
计算机硬件没有原子钟,无法做到精确计时
操作系统的计时函数本身就有少量偏差,由于js的计时器最终是调用操作系统的函数,也就有携带偏差
根据w3c的标准,浏览器实现计时器时,如果嵌套层级超过5层,会产生最少4毫秒的增量
事件循环会影响计时,计时器的回调函数只会在主线程空闲时运行,从而带来偏差
3、HTTP请求过程
HTTP(HyperText Transfer Protocol)请求是客户端与服务器之间进行通信的基础。理解HTTP请求的流程有助于更好地开发和调试Web应用程序。以下是HTTP请求的详细流程:
1. DNS解析
当客户端(通常是浏览器)需要访问一个URL时,首先需要将域名解析为IP地址。这一步通过DNS(Domain Name System)完成。DNS解析的过程如下:
- 浏览器检查本地缓存是否有该域名的IP地址。
- 如果本地缓存没有,浏览器会向本地DNS服务器发送查询请求。
- 本地DNS服务器检查其缓存,如果没有找到,会向上级DNS服务器查询,直到找到对应的IP地址。
- 找到IP地址后,返回给浏览器。
2. 建立TCP连接
一旦获得了服务器的IP地址,客户端需要与服务器建立TCP连接。这个过程通常通过三次握手(Three-Way Handshake)完成:
- 客户端发送SYN包:客户端向服务器发送一个SYN(synchronize)包,请求建立连接。
- 服务器回应SYN-ACK包:服务器收到SYN包后,回应一个SYN-ACK(synchronize-acknowledge)包,表示同意建立连接。
- 客户端发送ACK包:客户端收到SYN-ACK包后,发送一个ACK(acknowledge)包,确认连接建立。
3. 发送HTTP请求
TCP连接建立后,客户端可以发送HTTP请求。一个典型的HTTP请求包括以下部分:
请求行
:包括请求方法(如GET、POST)、请求URL和HTTP版本。
1
复制代码GET /index.html HTTP/1.1
请求头
:包含请求的元数据,如Host、User-Agent、Accept等。
1
2
3复制代码Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html空行:用于分隔请求头和请求体。
请求体:在POST请求中,包含要发送的数据(如表单数据、JSON等)。
4. 服务器处理请求
服务器接收到HTTP请求后,会进行以下处理:
- 解析请求:服务器解析请求行、请求头和请求体,提取出所需的信息。
- 处理请求:根据请求方法和URL,服务器执行相应的操作,如读取文件、查询数据库、执行业务逻辑等。
- 生成响应:服务器生成HTTP响应,包括状态行、响应头和响应体。
5. 发送HTTP响应
服务器处理完请求后,将HTTP响应发送回客户端。一个典型的HTTP响应包括以下部分:
状态行
:包括HTTP版本、状态码和状态描述。
1
复制代码HTTP/1.1 200 OK
响应头
:包含响应的元数据,如Content-Type、Content-Length、Set-Cookie等。
1
2复制代码Content-Type: text/html
Content-Length: 1234空行:用于分隔响应头和响应体。
响应体:包含实际的响应数据,如HTML文档、JSON数据、图片等。
6. 关闭连接
根据HTTP版本和请求头中的Connection字段,决定是否关闭TCP连接:
- HTTP/1.0:默认在每次请求后关闭连接,除非请求头中包含
Connection: keep-alive
。 - HTTP/1.1:默认保持连接,除非请求头中包含
Connection: close
。
如果需要关闭连接,客户端和服务器会通过四次挥手(Four-Way Handshake)来关闭TCP连接:
- 客户端发送FIN包:客户端向服务器发送一个FIN(finish)包,请求关闭连接。
- 服务器回应ACK包:服务器收到FIN包后,回应一个ACK包,确认关闭请求。
- 服务器发送FIN包:服务器发送一个FIN包,表示同意关闭连接。
- 客户端回应ACK包:客户端收到FIN包后,回应一个ACK包,确认连接关闭。
总结
HTTP请求的流程包括DNS解析、建立TCP连接、发送HTTP请求、服务器处理请求、发送HTTP响应和关闭连接。理解这些步骤有助于更好地开发和调试Web应用程序,优化网络性能和用户体验。
4、浏览器的渲染过程
整个渲染过程分为多个阶段,分别是:html解析、样式计算、布局、分层、生成绘制指令、分块、光栅化、绘制。
解析html
解析过程遇到css解析css,遇到js执行js,为了提高效率,浏览器在解析之前启动一个预解析的线程,率先下载html中外部css和外部js文件。
如果主线程解析到link位置,此时外部的css文件还没有下载解析好,主线程会继续执行后续html。因为下载和解析css的工作是处于预解析线程中进行,这就是css不会阻塞html的根本原因。
如果解析到script位置,会停止html而等待script下载好,并全局代码执行完毕后再继续执行html。因为js的执行可能会改变当前dom树,所以dom树的生成必须暂停,这就是js会阻塞html解析的根本原因。
会形成dom树和cssom树,浏览器的默认样式、内部样式、外部样式、行内样式都会包含在cssom树中。
样式计算
主线程会遍历得到dom树,依次为树中每个节点计算出它的最终样式,称之为computed style。
回流reflow会从此重新计算布局树,为了避免多次reflow导致反复计算,浏览器会合并这些操作,当js代码全部完成再进行统一的计算,所以改动造成的reflow是异步的。为了保证获取到最新的布局信息,在获取属性时则立即reflow。
布局
依次遍历每一个dom树节点,计算每个节点的几何信息,形成布局树,例如节点的宽高,相对包含块的位置。大部分时候布局树和dom树并非一一对应,例如display: none的节点没有几何信息,不会生成到布局树中;伪元素不存在于dom树中,会拥有几何信息,生成到布局树中;其他还有匿名行盒、匿名块盒等。
分层
主线程会使用一套复杂策略对布局树分层,好处在于将来改变一个层后,不会影响到其他层,提升效率。滚动条、堆叠上下文、transform、opacity、等样式或多或少会影响分层结果,will-change更大程度影响结果。
绘制
主线程会为每个分层产生绘制指令集,用于描述如何绘制。绘制主线程到此结束,剩余工作由合成线程完成。
重绘repaint会从此重新计算绘制指令
分块
合成线程会对每个图层分块,划分成更小的区域,它会从线程池中取多个线程完成分块工作。
光栅化
合成线程会将分块信息交给GPU进程完成光栅化,形成一块块位图,优先处理视口区域内的块。
绘制
合成线程拿到每一个位图后,生成一个个指引信息,提交给GPU进程,进而提交给GPU显卡硬件,完成屏幕成像。
变形发生在合成线程,与渲染主线程无关,不会影响布局也不会影响绘制指令,这就是transform效率高的本质原因。