在浏览器中输入URL后发生了什么 | 青训营笔记

213 阅读8分钟

在浏览器中输入URL后发生了什么 | 青训营笔记

首先,我们在浏览器的URL框内输入内容后,UI线程就会去判断我们输入的内容,看它到底是一个URL地址呢?还是一个query查询?

如果我们输入的是URL,则会去请求对应站点的资源,若我们输入的是query,就会将我们的输入直接发送给搜索引擎。

这就是为什么我们可以既可以在地址栏中输入URL地址,也可以输入我们要查询的内容。

browserProcess.png

这里讨论我们输入URL的情况

⭐建立连接&请求资源

当我们输入URL,按下回车,UI线程就会去通知网络线程发起一个网络请求,来获取站点内容

整个过程为:

  1. DNS域名解析,在域名服务器中获取域名对应的WEB服务器的IP地址。(如果本地有缓存,则优先读取本地缓存)

  2. 建立TCP连接:

    1. 第一次握手(SYN): 客户端发送一个带有 SYN(同步序列编号)标志的连接请求报文段给服务器。客户端进入 SYN_SENT 状态,等待服务器的确认。
    2. 第二次握手(SYN + ACK): 服务器收到客户端的连接请求报文后,如果同意建立连接,则会发送一个带有 SYN/ACK(同步序列编号和确认)标志的报文段作为响应,确认客户端的请求。服务器进入 SYN_RCVD 状态。
    3. 第三次握手(ACK): 客户端收到服务器的确认后,会发送一个带有 ACK 标志的报文段给服务器,表示连接已建立。服务器收到此确认报文段后,也进入 ESTABLISHED 状态。从此,客户端与服务器之间可以开始传输数据。

    以上就是TCP的三次握手,如此,客户端和服务器就成功连接,可以开始发送请求信息了

  3. 客户端发送Http Request请求信息,请求服务器的资源(页面)

  4. 这里并不是服务器直接收到请求,而是Nginx作为中间人收到了请求,它拿着请求再去找服务器,服务器将请求结果再递给Nginx,经由它手再返回给客户端。

如此,两者就建立了链接,并成功请求到了资源!

⭐读取响应

当浏览器的网络线程接收到HTTP响应后,会先去检查响应头的媒体类型(MIME Type)

如果响应主题是一个HTML文件,浏览器就会将内容交给渲染进程做处理

如果拿到的是其他类型的文件呢?例如Zip、exe等等,浏览器就会直接交给下载管理器进行下载,这就是为什么我们输入一些资源地址时会直接触发下载。

browserReacting.png

⭐寻找渲染进程

当网络线程做完所有检查以后,会被告知主进程数据已经准备完毕,主进程确认后会为这个站点寻找一个渲染进程

主进程通过IPC消息告知进程去处理本次导航,渲染进程接收到命令,就去开始接收数据,并告知主进程已经开始进行了处理,进入文档加载阶段。

browserRender.png

⭐资源加载

HTML页面开始加载啦!

同时我们也需要一些子资源,比如一些图片、CSS以及js脚本 首先开始加载时,会有一个预解析器对全局进行快速浏览:

  1. 移除无效的字符和修复基本的语法错误。
  2. 收集HTML结构信息,例如标签、属性和关系。这使得解析器在构建DOM树时可以更快地进行。
  3. 下载可能需要的外部资源,例如图像、字体、样式表和脚本文件。

正是由于预解析器的存在,CSS的加载和解析不会影响HTML的解析

但由于JS文件是同步的,所以即使预解析器提前调用网络线程对JS文件发起请求,在JS文件响应完成前,若主渲染线程执行到了该脚本文件处,就会停止解析,等待JS文件加载完毕后执行,这个问题我们可以通过为script添加async和defer来解决

browserGetResources.png

⭐开始渲染

🌰构建渲染树

  1. 构建DOM树和CSSOM树,将HTML文本以及CSS代码转化成浏览器能够理解的结构
  2. 构建渲染树,渲染树就是DOM和CSSOM的组合,通俗理解就是HTML作为主干结构,CSS作为叶片来进行修饰

browserDOM&CSSOM.png

🌰页面布局及绘制

根据渲染树计算每个节点的位置和大小,在浏览器页面区域绘制元素边框,遍历渲染树,生成布局树,再将元素以盒模型的形式写入文档流

PS:布局树与渲染树并不完全相同,有如下原因:

  • display:none的节点没有几何信息,不会加入到布局树
  • 伪元素节点会单独生成节点
  • 匿名行盒、匿名块盒等等

browserBox.png

最终就是绘制

  1. 构建图层:为特定的节点生成专属的图层,对整个布局树根据一系列复杂的策略进行分层,在后续若该层有变动,只会对该层进行处理,从而提高效率,滚动条、堆叠上下文、transform、opacity等样式都会影响分层结果,也可以通过will-change提前告知浏览器哪些属性将要变化,来更大程度影响分层结果
  2. 绘制图层:一个图层分为很多绘制指令(与canvas类似),然后将这些指令按顺序组成一个绘制列表,用于描述如何将内容绘制出来,将其交给合成线程
  3. 合成线程先从线程池中拿到多个线程来将每个图层进行分块,将其分成更多个小区域
  4. 合成线程将块信息交给GPU进程开始光栅化,并优先处理靠近视口的块,通过光栅化生成一块一块的位图
  5. 合成线程拿到每个层、每个块的位图后,生成一个个指引(quad)信息。指引信息中标识出了每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。 合成线程会把quad提交给GPU进程,由GPU进程产生系统调用,提交给GPU硬件,完成最终的屏幕成像

ps:变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。

⭐回流和重绘

🌰回流-reflow

reflow的本质就是重新计算layout树并完成后续操作。

当进行了会影响布局树的操作后,需要重新计算布局树,引发layout。

为了避免多次操作导致布局树反复计算,浏览器会合并这些操作,当JS代码全部完成后再及逆行统一计算。所以改动数据造成的reflow是异步完成的。

也同样因为如此,当JS获取布局属性时,就可能造成无法获取到最新的布局信息,浏览器在反复权衡下,最终决定获取属性时立即reflow。

🌰重绘-repaint

repaint的本质就是重新根据分层信息计算绘制指令。

当改动了可见样式后,就需要重新计算,会引发repaint。

由于元素的布局信息也属于可见样式,所以reflow一定会引起repaint

JS引擎解析过程

  • 创建window对象:window对象也叫全局执行环境,当页面产生时就被创建,所有的全局变量和函数都属于window的属性和方法,而DOM Tree也会映射在window的doucment对象上。当关闭网页或者关闭浏览器时,全局执行环境会被销毁。
  • 加载文件:完成js引擎分析它的语法与词法是否合法,如果合法进入预编译
  • 预编译:在预编译的过程中,浏览器会寻找全局变量声明,把它作为window的属性加入到window对象中,并给变量赋值为'undefined';寻找全局函数声明,把它作为window的方法加入到window对象中,并将函数体赋值给他(匿名函数是不参与预编译的,因为它是变量)。而变量提升作为不合理的地方在ES6中已经解决了,函数提升还存在。
  • 解释执行:执行到变量就赋值,如果变量没有被定义,也就没有被预编译直接赋值,在ES5非严格模式下这个变量会成为window的一个属性,也就是成为全局变量。string、int这样的值就是直接把值放在变量的存储空间里,object对象就是把指针指向变量的存储空间。函数执行,就将函数的环境推入一个环境的栈中,执行完成后再弹出,控制权交还给之前的环境。JS作用域其实就是这样的执行流机制实现的。