整体流程
从输入一个 URL、按下回车到显示页面,中间发生了什么?这道题既有广度,又有深度,很能考验一个人的知识体系。
-
解析URL:
- 当你在浏览器地址栏中输入URL后,浏览器首先解析URL,确定要访问的协议(如HTTP、HTTPS)、域名、端口以及具体的路径。
-
DNS解析:
- 浏览器查询DNS服务器,将域名转换成IP地址。如果浏览器缓存中没有这个IP,它会向本地DNS服务器发送请求,这个过程可能涉及递归查询直到找到对应的IP地址。
-
建立连接:
- DNS解析完成后,浏览器与服务器建立TCP/IP连接,如果是HTTPS还会进行SSL/TLS握手,加密连接。
-
发送请求:
- 连接建立后,浏览器发送HTTP或HTTPS请求到服务器,请求包含URL、HTTP版本、可接受的响应类型等信息。
-
接收响应:
- 服务器处理请求,生成响应并返回给浏览器。响应通常包括状态码、响应头和响应体。
-
解析HTML:
- 浏览器开始解析HTML文档,构建DOM树。解析是增量的,即浏览器会边下载边解析HTML,而不是等待整个HTML文件下载完毕。
-
CSS样式表加载与解析:
- 浏览器遇到
<link rel="stylesheet">或<style>标签时,会下载并解析CSS文件,创建CSSOM(CSS Object Model)。如果CSS文件中有网络资源(如字体、背景图片),这些资源也会被下载。
- 浏览器遇到
-
构建渲染树:
- 浏览器将DOM树和CSSOM合并,创建渲染树。渲染树中的每个节点都包含了足够的信息以显示在屏幕上。
-
布局/回流:
- 浏览器计算每个元素的位置和大小,这一过程称为布局或回流(Layout/Reflow)。
-
绘制/重绘:
- 完成布局后,浏览器将每个节点绘制到屏幕上,这一过程称为绘制或重绘(Painting/Repainting)。
-
执行JavaScript:
- 如果有内联或外部JavaScript脚本,浏览器会暂停HTML解析并执行这些脚本。脚本可能修改DOM、CSS,导致回流和重绘。
-
完成渲染:
- 所有资源加载完成,脚本执行完毕,页面最终渲染完成并显示给用户。
外部CSS样式是否阻塞解析HTML
下载并解析CSS文件一般不会阻塞HTML的解析。这是因为CSS文件的下载和解析可以在浏览器的其他线程中进行,而HTML解析通常发生在浏览器的主要渲染线程中。当浏览器遇到<link rel="stylesheet">标签时,它会异步地下载CSS文件,而不会阻止HTML解析继续进行。然而,在CSS文件下载完成之前,涉及到样式计算的任何渲染阶段可能会被延迟,因为浏览器需要CSSOM来构建渲染树。
理解建立TCP/IP连接
这通常是指TCP连接,因为它涉及到了真正的连接建立过程。而IP只是负责数据包的寻址和路由,它本身不建立也不维护连接。在互联网中,TCP连接是构建在IP协议之上的,也就是说,TCP数据传输依赖于IP来传送数据包,但IP并不知道也不关心这些数据包是否属于某个特定的TCP连接。
脚本如何阻塞HTML解析
当HTML文档中的脚本标记(<script>)被解析到时,浏览器的行为取决于几个因素:
-
同步脚本(默认行为):
- 如果
<script>标记没有async或defer属性,那么脚本是同步加载和执行的。这意味着HTML解析会暂停,直到脚本完全下载并执行完毕。这可能会影响到DOM树的构建和CSSOM的解析,因为脚本可能修改DOM结构或CSS样式。
- 如果
-
异步脚本(async):
- 当
<script>标记包含async属性时,脚本会在后台异步下载,并在下载完成后立即执行,无论文档解析是否完成。这种情况下,脚本执行不会阻止文档解析或渲染。
- 当
-
延迟脚本(defer):
- 当
<script>标记包含defer属性时,脚本同样异步下载,但在文档解析完成后且文档就绪之前执行。这意味着脚本在DOMContentLoaded事件触发前执行,但不会阻止页面的渲染。
- 当
通常,将脚本放在<body>的底部是一种最佳实践,因为它允许页面的主要内容快速加载和渲染,然后再加载和执行脚本。这样可以提高页面的感知性能,使用户能够更快地看到和交互页面内容。不过,如果脚本对页面的初始渲染至关重要,那么它可能需要在<head>中,并可能需要适当的同步处理。
混合使用async和defer
当在同一个页面中混合使用async和defer属性时,脚本的加载和执行顺序会根据每个脚本指定的属性而有所不同。具体来说:
-
带有
async属性的脚本(如A和D):- 这些脚本会异步加载,并且一旦下载完成就会立即执行,无论文档解析的状态如何。
- 由于
async脚本的执行是非确定性的,A和D脚本可能在任意时间点执行,且彼此之间以及与defer脚本之间的执行顺序无法保证。
-
带有
defer属性的脚本(如B和C):- 这些脚本也会异步加载,但它们会在文档解析完成之后、
DOMContentLoaded事件触发之前,按照它们在HTML文档中的出现顺序执行。 - 即使B和C脚本可能在A和D脚本下载完成之前就已经加载好了,它们也会等到文档解析完成之后才执行。
- 这些脚本也会异步加载,但它们会在文档解析完成之后、
建议
- 如果你有明确的依赖关系,考虑将所有相关脚本标记为
defer,并在HTML中按依赖顺序放置它们。 - 如果脚本之间没有依赖关系,可以使用
async来最大化并行加载和执行的优势,但要确保脚本的独立性。
总之,在复杂的页面中,考虑使用现代模块加载技术(如ES6模块)或动态导入(import()语句),这可以提供更强大和灵活的脚本加载和执行控制。
浏览器有哪些进程
下面是一个典型的浏览器(例如Chrome)中进程和线程的概述:
-
浏览器进程(Browser Process):
- 这是主进程,负责管理用户界面、存储设置、管理窗口和标签页等。
- 它还会管理其他进程的生命周期,比如创建新的渲染进程。
-
渲染进程(Renderer Process):
- 每个标签页都有自己的渲染进程,它们彼此隔离,以保证如果一个页面崩溃不会影响其他页面。
- 渲染进程负责解析HTML、CSS和JavaScript,以及渲染页面和执行脚本。
-
GPU进程(GPU Process):
- 负责图形处理,包括3D图形和视频解码,以减轻主CPU的负担并提高性能。
-
网络进程(Network Process):
- 处理所有网络请求,如下载资源(HTML、CSS、JS文件等)。
-
插件进程(Plugin Process):
- 用于运行插件,如Adobe Flash,以保持渲染进程的纯净和安全。