1. 输入URL到页面展示这个中间发生了什么
1.1. 解析URL
浏览器会将用户输入的URL字符串分解成不同的组成部分,以便后续的处理。URL的组成部分通常包括协议(如http、https)、主机名(即域名)、端口(可选,默认为80或443)、路径、查询参数和片段标识符(#后的部分,用于页面内定位,不发送给服务器
1.2. 判断缓存
浏览器查找并判断缓存
当用户在浏览器的地址栏输入URL并按下回车键后,浏览器首先会检查该URL对应的页面是否已经被缓存,以及缓存是否仍然有效。缓存的判断通常基于以下几个方面:
浏览器缓存
浏览器会检查自己的缓存中是否有该URL的页面。这包括检查页面内容(如HTML文档、CSS文件、JavaScript文件等)以及相关的资源文件是否已被缓存。
- 强缓存:如果浏览器缓存中存在该页面的强缓存(如Cache-Control或Expires头部信息指示的缓存时间未过期),则浏览器会直接使用缓存中的页面,而无需向服务器发送请求。
- 协商缓存:如果强缓存不存在或已过期,浏览器会检查是否存在协商缓存(如ETag或Last-Modified头部信息)。协商缓存需要浏览器向服务器发送一个带有验证信息的请求(如If-None-Match或If-Modified-Since头部),以询问服务器该资源自上次请求以来是否已被修改。如果服务器确认资源未修改(返回304 Not Modified状态码),则浏览器可以使用缓存中的页面;如果服务器确认资源已修改(返回200 OK状态码及新的资源内容),则浏览器会下载新的资源。
代理服务器缓存
除了浏览器缓存外,用户还可能通过代理服务器(如CDN、反向代理等)访问Web页面。代理服务器也会维护自己的缓存,以加速对常用资源的访问。当浏览器请求某个URL时,代理服务器会先检查自己的缓存中是否有该资源的副本,并根据缓存策略决定是否直接从缓存中提供资源给浏览器,或向原始服务器发起请求以获取最新资源。
1.3. DNS解析
DNS解析是指当用户或应用程序通过浏览器、电子邮件客户端等访问某个域名时,系统会通过DNS协议查询对应的IP地址的过程。这个过程涉及从本地缓存、递归DNS服务器到权威DNS服务器等一系列查询操作,最终获取到域名映射的实际IP地址。
当用户输入一个域名并请求访问时,DNS解析过程大致如下:
- 客户端查询本地缓存:首先,客户端(如浏览器)会检查自己的缓存中是否有该域名的解析记录。
- 查询本地DNS服务器:如果本地缓存中没有记录,客户端会向配置的本地DNS服务器发送查询请求。 本地DNS服务器处理查询:
- 检查本地缓存:本地DNS服务器会先检查自己的缓存中是否有该域名的解析记录。
- 递归查询或迭代查询:如果本地缓存中没有记录,本地DNS服务器会向其他DNS服务器发起查询。根据配置和策略,它可能会采用递归查询或迭代查询的方式。
- 获取权威DNS服务器的响应:经过一系列的查询后,最终会找到权威DNS服务器,并获取到该域名对应的IP地址。
- 返回结果给客户端:本地DNS服务器将获取的IP地址返回给客户端,客户端随后使用该IP地址与服务器建立连接。
1.4. 获取MAC地址
MAC地址的获取通常发生在数据传输的底层,即数据链路层。当数据包准备从一台设备发送到另一台设备时,发送方需要知道接收方的MAC地址。然而,在输入URL到页面展示的过程中,用户或浏览器并不直接参与MAC地址的获取。
1.4.1 ARP协议
在实际的数据传输过程中,如果发送方和接收方位于同一局域网内,发送方会使用ARP(地址解析协议)来查询接收方的MAC地址。ARP协议通过广播一个ARP请求到局域网内的所有设备,询问“哪个设备拥有这个IP地址?”。接收方(如果它拥有该IP地址)会回复一个ARP应答,其中包含其MAC地址。
1.4.2 路由与网关
如果发送方和接收方不在同一局域网内,数据包需要通过路由器进行转发。在这种情况下,发送方会先查询默认网关的MAC地址(通常是通过ARP协议获得的),然后将数据包发送给网关。网关会负责将数据包转发到下一个网络,并重复这个过程,直到数据包到达目的网络并由该网络中的设备接收。
1.5. TCP三次握手,建立连接
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在数据传输之前,TCP需要通过三次握手来建立连接,确保通信双方都已准备好接收数据。
1.5.1. 第一次握手
客户端发送SYN包:客户端(即浏览器)向服务器发送一个SYN(同步序列编号)包,并设置序列号(seq=x),表示希望建立连接。此时,客户端进入SYN_SENT状态,等待服务器的确认。
1.5.2. 第二次握手
服务器回复SYN+ACK包:服务器收到客户端的SYN包后,会回复一个SYN+ACK(同步/确认)包。在这个包中,服务器会确认客户端的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),表示自己也希望建立连接。此时,服务器进入SYN_RCVD状态。
1.5.3. 第三次握手
客户端发送ACK包:客户端收到服务器的SYN+ACK包后,会再次向服务器发送一个ACK(确认)包,确认服务器的SYN(ack=y+1)。此包发送完毕后,客户端和服务器都进入ESTABLISHED状态,表示TCP连接已经成功建立。此时,双方可以开始传输数据。
1.6. 建立HTTP/HTTPS连接
1.6.1. HTTP连接的建立
-
在TCP连接建立之后,客户端和服务器就可以开始HTTP协议的交互了。HTTP协议是基于请求/响应模型的,客户端发起请求,服务器返回响应。
-
客户端发送HTTP请求:客户端通过TCP连接向服务器发送HTTP请求,请求中包含了请求的资源、请求的方法(如GET、POST等)、请求头等信息。
-
服务器返回HTTP响应:服务器收到客户端的请求后,会处理请求并返回相应的HTTP响应。响应中包含了状态码、响应头、响应体等信息。
-
断开TCP连接(可选):在HTTP/1.0中,每次请求/响应完成后都会断开TCP连接。但在HTTP/1.1中,支持持久连接(Keep-Alive),可以在一次TCP连接中发送多个请求/响应,提高了效率。
1.6.1. HTTPS连接的建立
HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)是在HTTP的基础上加入了SSL/TLS协议层,以实现数据加密和身份验证。HTTPS连接的建立过程比HTTP更加复杂,主要包括以下几个步骤:
- 客户端发送SSL/TLS协议版本的Client Hello消息:客户端首先向服务器发送一个Client Hello消息,其中包含客户端支持的SSL/TLS协议版本、加密套件等信息。
- 服务器发送Server Hello消息及证书:服务器收到Client Hello消息后,会选择一个双方都支持的SSL/TLS协议版本和加密套件,并发送一个Server Hello消息给客户端。同时,服务器还会发送自己的证书给客户端,证书中包含了服务器的公钥等信息。
客户端验证服务器证书:客户端收到服务器证书后,会验证证书的合法性。如果证书合法,客户端会生成一个随机数作为会话密钥,并使用服务器的公钥对会话密钥进行加密,然后将加密后的会话密钥发送给服务器。 - 服务器解密会话密钥:服务器收到加密后的会话密钥后,会使用自己的私钥进行解密,得到会话密钥。此后,双方就可以使用会话密钥进行对称加密通信了。
- 开始HTTPS通信:在会话密钥协商完成后,客户端和服务器就可以开始HTTPS通信了。与HTTP通信类似,客户端发起请求,服务器返回响应。但不同的是,HTTPS通信中的数据都是经过加密的,确保了数据传输的安全性。
1.7. 数据返回
服务器接收到HTTP/HTTPS请求后,会解析请求,并根据请求的内容进行相应的处理。处理完成后,服务器会构建HTTP/HTTPS响应,并通过TCP连接发送给浏览器。响应中包含了状态码、响应头、响应体等信息。
1.8. 页面渲染
当浏览器接收到服务器的响应后,会开始页面渲染的过程。页面渲染主要包括以下几个步骤:
- 解析HTML文档:浏览器使用HTML解析器将HTML文档解析成DOM(文档对象模型)树。DOM树是页面结构的内存表示,包含了页面中的所有元素和它们的层级关系。
- 解析CSS样式:同时,浏览器会解析CSS文件,将CSS样式表转化为浏览器可以理解的styleSheets,并计算出DOM节点的样式。这些样式信息将用于后续的页面渲染。
- 构建渲染树:浏览器将DOM树和样式信息结合起来,构建渲染树(Render Tree)。渲染树只包含需要显示的节点和它们的样式信息,用于后续的布局和绘制。
- 布局计算:浏览器根据渲染树中的信息,计算每个节点的位置和大小,确定它们在页面上的布局。
- 绘制页面:最后,浏览器根据渲染树和布局信息,绘制页面内容,并将其显示在屏幕上。这个过程可能涉及到多个图层的合成和绘制,以提高渲染效率和性能。
1.9. TCP四次挥手,断开连接
TCP四次挥手(Four-way handshake)是TCP/IP协议中用于关闭一个已经建立的TCP连接的标准过程。这个过程确保双方都能正常地关闭连接,并释放系统资源。下面详细解释TCP四次挥手的步骤:
TCP四次挥手步骤
- 客户端发送FIN报文
当客户端(或称为数据发送方)想要关闭连接时,它会向服务器(或称为数据接收方)发送一个FIN(结束)报文段,并设置序列号为u(等于前面已传送过的数据的最后一个字节的序列号加1)。这个FIN报文段表示客户端已经没有数据要发送了,请求关闭连接。 - 服务器发送ACK报文
服务器收到客户端的FIN报文后,会发送一个ACK报文作为应答,其确认号为u+1,表示对客户端FIN报文的确认。此时,TCP连接处于半关闭状态,即客户端到服务器的连接已经关闭,但服务器到客户端的连接仍然打开,服务器还可以继续发送数据给客户端。 - 服务器发送FIN报文
当服务器也完成了所有数据的发送,并且想要关闭连接时,它会向客户端发送一个FIN报文段,并设置序列号为v(等于服务器前面已传送过的数据的最后一个字节的序列号加1)。这个FIN报文段表示服务器也已经没有数据要发送了,请求关闭连接。 - 客户端发送ACK报文
客户端收到服务器的FIN报文后,会发送一个ACK报文作为应答,其确认号为v+1,表示对服务器FIN报文的确认。此时,TCP连接被完全关闭,双方都不再发送数据。
2. 渲染原理
浏览器的渲染原理是一个复杂且高效的过程,它涉及到多个步骤和组件的协同工作,以确保网页内容能够准确、快速地展示在用户界面上。以下是浏览器渲染原理的详细步骤说明:
2.1. 解析HTML(Parse HTML)
- 过程:当用户在浏览器中输入网址或点击链接时,浏览器会向服务器发送请求,获取网页的HTML文件。浏览器随后开始解析HTML文件,将HTML标签和文本转换为浏览器能够理解的结构——文档对象模型(DOM)。
- 输出:DOM树,表示了网页的结构和内容。
2.2. 样式计算(Style Calculation)
- 过程:在解析HTML的同时或之后,浏览器会遇到CSS样式表(可能是内联样式、外部样式表或用户代理样式)。浏览器会根据CSS选择器匹配DOM树中的元素,并应用相应的样式规则。经过选择器匹配、继承处理和优先级计算后,浏览器会得到每个元素的最终样式属性,这个过程称为样式计算。
- 输出:每个元素的ComputedStyle,即带有最终样式的DOM树。
2.3. 布局(Layout)
- 过程:浏览器会遍历带有样式的DOM树(渲染树),根据每个元素的样式信息(如位置、大小等)计算出它们在页面中的准确位置。这个过程也被称为重排(Reflow)。对于非静态定位的元素,浏览器会根据其定位属性(如相对定位、绝对定位、固定定位)进行特殊的布局计算。
- 输出:每个元素的几何信息,如宽、高、位置等。
2.4. 分层(Layering)
- 过程:为了提高渲染效率和性能,浏览器会将页面中的元素按照一定规则分成多个图层(Layer)。每个图层可以独立地进行绘制和渲染。分层的依据包括元素的3D或透视变换属性、CSS动画或过渡效果、特殊元素(如
<video>
、<canvas>
、<iframe>
)以及CSS滤镜和混合模式效果等。 - 输出:多个独立的图层。
2.5. 绘制(Painting)
- 过程:浏览器会为每个图层生成绘制指令集,这些指令集描述了如何将图层的内容绘制出来。绘制指令集包括元素的位置、大小、颜色、阴影等信息,以及如何对页面元素进行变换、裁剪等操作。最终,浏览器会将渲染树中的每个元素转换为屏幕上的实际像素,形成绘制表面(Paint Surface)。
- 输出:每个图层的绘制表面。
2.6. 分块(Tiling)和光栅化(Rasterization)
- 分块:为了提高渲染效率,浏览器会将图层划分为多个小块区域(Tile),以便并行处理。
- 光栅化:浏览器将图层的矢量图形(如线条、路径等)转换为屏幕上的像素(位图)。这个过程支持硬件加速,可以利用GPU等硬件资源来提高渲染效率。
2.7. 合并(Compositing)
- 过程:在所有图层的光栅化完成后,浏览器会将它们合并成一个最终的图像,并显示在屏幕上。这个过程是由合成线程(Compositing Thread)负责的,它会处理图层的叠加、透明度、动画等效果。
- 输出:最终的屏幕图像。
注意事项
- 在渲染过程中,如果遇到JavaScript代码,浏览器会暂停渲染过程,执行JavaScript代码。因为JavaScript代码可能会修改DOM树或样式表,从而影响渲染结果。
- 浏览器的渲染性能受到多种因素的影响,包括硬件性能、网页的复杂程度、网络状况等。因此,优化网页结构和代码是提高渲染性能的重要手段。
通过以上步骤,浏览器能够将HTML、CSS和JavaScript等静态资源转化为可视化的网页界面,并展示给用户。
3. 浏览器的回流与重绘 (Reflow & Repaint)
回流(Reflow)与重绘(Repaint)是浏览器在渲染页面时涉及的两个重要过程,它们对网页的性能和用户体验有着直接影响。
3.1. 回流(Reflow)
定义:
回流,也称为布局(Layout),是指浏览器为了重新计算文档中元素的几何属性(如位置、大小等)而需要重新计算布局的过程。当页面中的元素发生变化(如尺寸、位置、内容变化、新增或删除元素等)时,浏览器需要重新计算这些变化对其他元素的影响,这个过程就是回流。
触发场景:
- 修改DOM元素的布局属性,如
width
、height
、margin
、padding
等。 - 修改DOM元素的位置,如
top
、left
、right
、bottom
等。 - 修改字体大小。
- 改变窗口大小。
- 查询某些布局属性,如
offsetWidth
、offsetHeight
、clientWidth
、clientHeight
等。
性能影响:
回流是一个相对耗费性能的操作,因为它需要遍历整个DOM树,并重新计算每个元素的几何属性。如果频繁触发回流,会导致页面的渲染性能下降,甚至出现页面卡顿的情况。
3.2. 重绘(Repaint)
定义:
重绘是指当元素的外观发生变化(如颜色、背景色等),但没有改变布局时,浏览器会重新绘制这些元素的过程。重绘不会影响页面的布局,因此比回流的开销要小。
触发场景:
- 修改元素的颜色,如
color
、background-color
等。 - 修改元素的透明度,如
opacity
。 - 修改元素的轮廓,如
outline
。
性能影响:
虽然重绘的开销相对较小,但过多的重绘仍然会对性能产生一定影响。因此,在开发中也需要尽量避免不必要的重绘操作。
3.3. 总结
- 回流必然导致重绘:当发生回流时,浏览器需要重新计算元素的几何属性,然后重新绘制这些元素,因此回流必然会导致重绘。
- 重绘不一定导致回流:如果只是修改元素的外观属性(如颜色),不会影响布局,只会触发重绘。
3.4. 优化建议
为了减少回流和重绘对性能的影响,可以采取以下优化策略:
- 尽量避免频繁操作DOM,尽量将多次操作合并成一次。
- 使用
DocumentFragment
进行批量DOM操作。 - 避免使用触发回流的CSS属性,如
float
、position: absolute
等。 - 使用CSS3动画,因为它们在GPU上运行,不会触发回流和重绘,性能更好。
- 缓存查询结果,避免频繁查询触发回流。
- 在动画循环中使用
requestAnimationFrame
,确保动画在每一帧中只进行一次回流和重绘。
综上所述,了解回流和重绘的原理并采取相应的优化措施,可以显著提高页面的渲染性能和用户体验。
4. Web Components:构建现代、可维护的 Web 应用
Web Components 是一套不同的技术,允许你创建可复用的自定义元素——带有封装样式和行为的HTML标签。这些自定义元素可以在任何现代浏览器中直接使用,无需额外的库或框架。使用Web Components可以帮助你构建现代、可维护的Web应用,因为它们提供了高度的封装性和可重用性。
4.1. Web Components 的核心技术
Web Components 主要由以下三个关键技术组成:
- Custom Elements(自定义元素):允许你定义自己的HTML元素,这些元素可以包含自己的属性和方法。
- Shadow DOM(影子DOM):提供了一种封装内部DOM树的方式,使得内部DOM对外部不可见,从而避免了样式和JavaScript的冲突。
- HTML Templates(HTML模板):允许你定义在页面中不立即渲染的HTML标记,这些模板可以在需要时通过JavaScript动态地插入到DOM中。
4.2. 构建现代、可维护的Web应用的优势
-
封装性:
- Shadow DOM 提供了强大的封装能力,使得组件的样式和内部逻辑与外部完全隔离。这减少了样式冲突和全局命名空间的污染。
-
可重用性:
- Custom Elements 允许你创建可复用的HTML元素,这些元素可以在不同的项目中重复使用,提高了开发效率。
-
维护性:
- 由于组件的封装性和独立性,当需要修改或更新组件时,可以更容易地定位问题,并且不会影响到其他部分的代码。
-
渐进式增强:
- Web Components 可以在不支持它们的旧版浏览器中优雅降级,而在支持它们的现代浏览器中提供增强的用户体验。
-
跨框架兼容性:
- Web Components 是基于Web标准的,因此它们可以在任何支持这些标准的框架(如React、Vue、Angular等)中使用,提高了跨框架的兼容性。
4.3. 实践建议
-
学习基础:
- 深入理解Custom Elements、Shadow DOM和HTML Templates的基本原理和用法。
-
逐步引入:
- 在现有项目中逐步引入Web Components,可以从简单的组件开始,逐步替换或增强现有的UI组件。
-
性能优化:
- 注意Web Components的性能优化,比如合理使用Shadow DOM的封装性来减少不必要的DOM操作,以及利用HTML Templates来减少DOM的初始加载时间。
-
社区和工具:
- 关注Web Components的社区和工具链发展,利用现有的库和工具来加速开发过程。
-
文档和测试:
- 为你的Web Components编写详细的文档和测试用例,确保它们易于理解和维护。
通过利用Web Components,你可以构建出更加模块化、可维护和可复用的Web应用,从而提高开发效率和用户体验。
4.4. 示例
当然,下面我将通过一个简单的例子来展示如何使用Web Components来构建一个现代、可维护的Web应用组件。我们将创建一个自定义的按钮组件,该组件将使用Custom Elements和Shadow DOM来封装其样式和行为。
定义Custom Element
首先,我们需要定义一个自定义元素。在这个例子中,我们将创建一个名为<my-button>
的按钮元素。
class MyButton extends HTMLElement {
constructor() {
super(); // 调用父类的constructor
// 创建一个shadow root
const shadow = this.attachShadow({mode: 'open'});
// 使用HTML模板来填充shadow DOM
const template = document.createElement('template');
template.innerHTML = `
<style>
button {
background-color: #4CAF50; /* 绿色 */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
<button>Click Me!</button>
`;
// 将模板内容克隆到shadow DOM中
shadow.appendChild(template.content.cloneNode(true));
// 监听按钮点击事件
shadow.querySelector('button').addEventListener('click', () => {
alert('Button clicked!');
});
}
}
// 定义元素
customElements.define('my-button', MyButton);
在HTML中使用自定义元素
现在,你可以在任何HTML文件中使用<my-button>
元素了,就像使用普通的HTML元素一样。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Components Example</title>
<!-- 引入自定义元素的JavaScript代码 -->
<script src="my-button.js"></script>
</head>
<body>
<!-- 使用自定义的<my-button>元素 -->
<my-button></my-button>
</body>
</html>