浏览器渲染原理

1、JS的异步

js是单线程的语言,它运行在浏览器的渲染主线程中,而渲染主线程只有一个。

渲染主线程承担着诸多工作,例如解析、渲染页面、执行js都在其中,如果使用同步会极有可能产生阻塞,从而导致消息队列中许多其他任务无法得到执行,造成1主线程白白的消耗时间 2页面无法及时更新,产生页面卡死现象

所以浏览器采用异步的方式来解决,具体做法是(事件循环)当某些任务发生时,例如计时器、网络、事件监听,主线程将其交给其他线程处理,自身继续执行后续代码,当其他线程结束后,将事先传递的回调函数包装成任务加入到消息队列末尾,等待主线程的调度执行

在这种异步模式下,浏览器永不阻塞,最大限度的保证了单线程的流畅运行。

2、事件循环

事件循环又称消息循环,是浏览器渲染主进程的工作方式

在Chrome的源码中,它开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,其他线程只需要在合适的时候将任务加入到消息队列的末尾中即可。

早期把消息队列分为宏任务和微任务,这种说法已经无法满足复杂的浏览器环境,取而代之的是一种更灵活的方式,例如交互队列、延时队列、事件,根据w3c的解释,每个任务都有不同的类型,同类型的任务必须在同一个队列中,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级,在一次事件循环中,有浏览器自行决定取哪一个队列的任务,但必须有一个微队列,并且优先级最高

js计时器能否精确计时?

不行,原因:

  1. 计算机硬件没有原子钟,无法做到精确计时

  2. 操作系统的计时函数本身就有少量偏差,由于js的计时器最终是调用操作系统的函数,也就有携带偏差

  3. 根据w3c的标准,浏览器实现计时器时,如果嵌套层级超过5层,会产生最少4毫秒的增量

  4. 事件循环会影响计时,计时器的回调函数只会在主线程空闲时运行,从而带来偏差

3、HTTP请求过程

HTTP(HyperText Transfer Protocol)请求是客户端与服务器之间进行通信的基础。理解HTTP请求的流程有助于更好地开发和调试Web应用程序。以下是HTTP请求的详细流程:

1. DNS解析

当客户端(通常是浏览器)需要访问一个URL时,首先需要将域名解析为IP地址。这一步通过DNS(Domain Name System)完成。DNS解析的过程如下:

  1. 浏览器检查本地缓存是否有该域名的IP地址。
  2. 如果本地缓存没有,浏览器会向本地DNS服务器发送查询请求。
  3. 本地DNS服务器检查其缓存,如果没有找到,会向上级DNS服务器查询,直到找到对应的IP地址。
  4. 找到IP地址后,返回给浏览器。

2. 建立TCP连接

一旦获得了服务器的IP地址,客户端需要与服务器建立TCP连接。这个过程通常通过三次握手(Three-Way Handshake)完成:

  1. 客户端发送SYN包:客户端向服务器发送一个SYN(synchronize)包,请求建立连接。
  2. 服务器回应SYN-ACK包:服务器收到SYN包后,回应一个SYN-ACK(synchronize-acknowledge)包,表示同意建立连接。
  3. 客户端发送ACK包:客户端收到SYN-ACK包后,发送一个ACK(acknowledge)包,确认连接建立。

3. 发送HTTP请求

TCP连接建立后,客户端可以发送HTTP请求。一个典型的HTTP请求包括以下部分:

  1. 请求行

    :包括请求方法(如GET、POST)、请求URL和HTTP版本。

    1
    复制代码GET /index.html HTTP/1.1
  2. 请求头

    :包含请求的元数据,如Host、User-Agent、Accept等。

    1
    2
    3
    复制代码Host: www.example.com
    User-Agent: Mozilla/5.0
    Accept: text/html
  3. 空行:用于分隔请求头和请求体。

  4. 请求体:在POST请求中,包含要发送的数据(如表单数据、JSON等)。

4. 服务器处理请求

服务器接收到HTTP请求后,会进行以下处理:

  1. 解析请求:服务器解析请求行、请求头和请求体,提取出所需的信息。
  2. 处理请求:根据请求方法和URL,服务器执行相应的操作,如读取文件、查询数据库、执行业务逻辑等。
  3. 生成响应:服务器生成HTTP响应,包括状态行、响应头和响应体。

5. 发送HTTP响应

服务器处理完请求后,将HTTP响应发送回客户端。一个典型的HTTP响应包括以下部分:

  1. 状态行

    :包括HTTP版本、状态码和状态描述。

    1
    复制代码HTTP/1.1 200 OK
  2. 响应头

    :包含响应的元数据,如Content-Type、Content-Length、Set-Cookie等。

    1
    2
    复制代码Content-Type: text/html
    Content-Length: 1234
  3. 空行:用于分隔响应头和响应体。

  4. 响应体:包含实际的响应数据,如HTML文档、JSON数据、图片等。

6. 关闭连接

根据HTTP版本和请求头中的Connection字段,决定是否关闭TCP连接:

  1. HTTP/1.0:默认在每次请求后关闭连接,除非请求头中包含Connection: keep-alive
  2. HTTP/1.1:默认保持连接,除非请求头中包含Connection: close

如果需要关闭连接,客户端和服务器会通过四次挥手(Four-Way Handshake)来关闭TCP连接:

  1. 客户端发送FIN包:客户端向服务器发送一个FIN(finish)包,请求关闭连接。
  2. 服务器回应ACK包:服务器收到FIN包后,回应一个ACK包,确认关闭请求。
  3. 服务器发送FIN包:服务器发送一个FIN包,表示同意关闭连接。
  4. 客户端回应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效率高的本质原因。