🚀 面试官问:从 URL 输入到页面展示,中间到底经历了什么?

0 阅读9分钟

👋 前言:为什么要死磕这道题?

在前端面试的江湖里,有一道题如同“倚天剑”般存在,它就是:“从浏览器地址栏输入 URL 到页面展示,中间经历了什么?”

这道题的出现率高达 80%,绝对的“经典考题”。为什么面试官这么爱问?因为它不是一道简单的填空题,而是一张知识图谱。它像一条线,串起了:

  • 🎨 前端渲染(DOM 树、CSSOM、布局、绘制)
  • 🌐 计算机网络(DNS、TCP/IP、HTTP/HTTPS)
  • 💻 操作系统(进程、线程、IPC 通信)
  • 🛡️ Web 安全(XSS、CSRF、安全沙箱)

回答策略:千万不要像背书一样崩知识点!要像讲故事一样,按知识体系时间线,清晰地组织表达逻辑。今天,我们就把这个过程拆解得明明白白!


🏗️ 第一部分:宏观视角 —— 浏览器的多进程架构

在深入细节之前,我们需要先了解“舞台”的构造。现在的浏览器(以 Chrome 为例)可不是一个简单的单体应用,它是一个多进程(Multi-Process) 的复杂系统。

1.1 进程(Process) vs 线程 (Thread)

这也是操作系统课上的必考点:

  • 进程:操作系统分配资源的最小单元。你可以把它想象成一个“工厂”,拥有独立的资源(内存等)。
  • 线程:操作系统执行的最小单元。它是工厂里的“工人”,一个进程可以有多个线程,它们共享进程的资源。

1.2 浏览器的“部门协作”

浏览器采用了多进程架构,这意味着它把不同的工作交给了不同的“部门”去处理,以保证稳定性和流畅度。整个导航过程,主要涉及以下几个核心进程的配合:

进程名称核心职责这里的角色
Browser Process
(浏览器主进程)
就像浏览器的“CEO”。负责界面显示(地址栏、书签)、用户交互、子进程管理、存储,历史记录(前进,回退)等。总指挥
Network Process
(网络进程)
面向浏览器进程和渲染进程提供网络资源的加载。物流部门
Renderer Process
(渲染进程)
负责将 HTML、CSS、JS 转化为用户可交互的网页。通常一个 Tab 一个渲染进程(出于安全和沙箱考虑)。施工队

💡 思考:为什么浏览器要搞这么麻烦的多进程? 主要是为了稳定性。如果所有页面都在一个进程里,某一个 Tab 崩溃了(比如写了个死循环),整个浏览器都会崩掉。多进程架构下,一个 Tab 崩了,仅是该渲染进程挂掉,不影响其他页面。


🎬 第二部分:导航的起点 —— 用户输入

当你在地址栏输入字符时,浏览器主进程就开始忙活了。

2.1 这里的输入是 URL 还是搜索词?

浏览器主进程会分析你输入的内容:

  • 如果是搜索关键词:它会使用默认搜索引擎(如 Google 或 Baidu),合成带有查询参数的 URL。
    • Example: 输入 前端面试 -> https://www.google.com/search?q=前端面试
  • 如果是符合规则的 URL:它会进行自动补全,加上协议头(Protocol)。
    • Example: 输入 time.geekbang.org -> http://time.geekbang.org (补全 http://https://)

2.2 站住!你要离开吗?(beforeunload)

在正式跳转之前,浏览器会给当前页面一个“交代后事”的机会。这就是 beforeunload 事件。

📝 代码实战: 假如你正在写一篇长文,突然手滑关了标签页,网页弹窗提示“你有未保存的内容”,就是在这个阶段做到的。

<!-- 📄 1.html -->
<!DOCTYPE html>
<html lang="en">
<body>
    <h1>试着关闭这个标签页,或者刷新页面</h1>
    <script>
        // 🛑 监听 beforeunload 事件
        window.addEventListener('beforeunload', function (e) {
            console.log('beforeunload 事件触发了');
            // 标准做法:阻止默认事件
            e.preventDefault();
            // 某些浏览器需要设置 returnValue
            e.returnValue = ''; 
        });

        // 💡 补充知识:pagehide
        // 现代浏览器有 bfcache (Back-Forward Cache),页面可能只是被“冻结”而非卸载
        // 所以通常也会监听 pagehide 作为补充
        window.addEventListener('pagehide', function (e) {
            if (e.persisted) {
                console.log('⚠️ 页面进入 bfcache,并未完全卸载');
            } else {
                console.log('✅ 页面正常卸载');
            }
        });
    </script>
</body>
</html>

如果当前页面没有阻止跳转,浏览器主进程就会进入 Loading 状态(地址栏上的旋转图标开始转动),并向网络进程发送指令:“嘿,去帮我下载这个 URL 的资源!”


📡 第三部分:网络的迷宫 —— 网络进程启动

网络进程接收到 URL 请求后,真正的“硬仗”开始了。

3.1 缓存检查 (Cache Check)

网络进程首先会检查本地是否有缓存(Local Cache)。

  • 如果有强缓存(Cache-Control, Expires)且未过期:直接拦截请求,返回缓存资源。状态码 200 (from disk cache)200 (from memory cache)
  • 如果没有或已过期:继续发起网络请求。

3.2 DNS 解析:寻找服务器的“门牌号”

URL 只是给人看的,计算机只认 IP 地址。我们需要 DNS (Domain Name System) 来把域名翻译成 IP。

这是一个递归查找的过程,就像查户口一样:

  1. 本地 DNS 缓存:浏览器缓存 -> 操作系统缓存 (hosts 文件)。
  2. 局域网 DNS:路由器缓存。
  3. ISP DNS:比如电信/联通的本地 DNS 服务器。
  4. 根域名服务器 -> 顶级域名服务器 (.com, .org) -> 权威域名服务器

🌐 冷知识:DNS 是一个分布式的数据库。你访问过的网站,DNS 记录通常会缓存在你的系统或 ISP 附近。所以,大家访问得多的网站,通常解析速度也更快,这就是“前人栽树,后人乘凉”。

3.3 建立连接:TCP 三次握手

拿到 IP 后,浏览器通过 OSI 七层模型 向下封装数据。为了确保双方(客户端和服务器)都有发送和接收的能力,必须进行 TCP 三次握手

  1. SYN: 你好,我想跟你聊天。(Client -> Server)
  2. SYN + ACK: 好的,我也想跟你聊。(Server -> Client)
  3. ACK: 那我们就开始吧!(Client -> Server)

注:如果是 HTTPS,这里还需要进行 TLS/SSL 四次握手,交换密钥,确保数据加密传输。现在强制使用https,如果输入http会重定向到https

3.4 发送 HTTP 请求 & 接收响应

连接建立后,网络进程构建请求包:

  • 请求行GET /index.html HTTP/1.1
  • 请求头:携带 CookieAuthorization (JWT Token)、User-Agent 等重要信息。

服务器处理后,返回响应头响应体

⚠️ 重点插播:HTTP 重定向 (301 vs 302 vs 307)

readme.md 中特别提到了这一点。有时候服务器不会直接给你网页,而是给你一个“新地址”。

  • 301 Moved Permanently (永久重定向)
    • 告诉浏览器:“这房子以后都归新地址了,别来老地方了。”
    • 场景:域名更换,http 迁移到 https。
    • 影响:浏览器会缓存这个重定向,下次直接去新地址,不问服务器了。
  • 302 Found (临时重定向)
    • 告诉浏览器:“我暂时搬家了,你先去新地址,但下次还得来问我。”
    • 场景:活动页面跳转,未登录跳转登录页。
  • 307 Temporary Redirect
    • 严谨版 302。302 有个历史遗留问题,某些浏览器会在重定向时把 POST 请求变成 GET。307 严格规定:保持原有的请求方法不变(POST 还是 POST)。

🔄 第四部分:关键转折点 —— 提交文档 (Commit Navigation)

这是很多面试者的盲区,也是流程图中最核心的环节。

当网络进程接收到服务器返回的响应头(Response Headers)后,它需要根据 Content-Type 做决定:

  • 如果是 application/octet-stream(文件):交给下载管理器,导航结束。
  • 如果是 text/html(网页):通知浏览器主进程,准备渲染!

这里发生了复杂的 IPC (进程间通信)

4.1 准备渲染进程

浏览器主进程收到网络进程的 “响应就绪” 信号后,会检查当前 URL 是否可以复用已有的渲染进程(比如同站点策略)。如果不行,就启动一个新的渲染进程。

4.2 提交文档 (The "Commit" Moment)

重点描述步骤:

  1. 发起提交:浏览器主进程向渲染进程发送 “提交导航 (Commit Navigation)” 的消息。
  2. 建立数据管道:渲染进程收到消息后,直接和网络进程建立数据传输管道,准备接收响应体数据(HTML)。
    • 注意:这里不需要浏览器主进程做“二传手”,数据直接由网络进程流向渲染进程,效率极高!
  3. 确认提交:渲染进程读取到数据流后,向浏览器主进程发送 “确认提交” 消息,准备好接收和解析页面信息。
  4. 界面更新
    • 浏览器主进程收到“提交文档”后,移除旧文档。
    • 更新地址栏(此时 URL 才真正变过来)。
    • 更新历史记录(前进后退按钮可用)。
    • 页面进入 Loading 状态(原本的旧页面消失,显示白屏或新页面的背景)。

🖼️ 画面感:这就是为什么有时候你点了链接,地址栏还没变,页面也没变,过了一会儿突然地址栏变了,旧页面也没了。那个“卡顿”的时间,就是网络请求和进程通信的时间。


🎨 第五部分:渲染进程的独角戏 —— 页面解析与加载

虽然导航结束了,但对前端来说,好戏才刚刚开始。渲染进程拿到了 HTML 数据,开始干活:

  1. 构建 DOM 树:HTML 解析器将字符流转化为节点。
  2. 样式计算:解析 CSS,生成 Computed Style。
  3. 布局 (Layout):计算元素几何位置,生成布局树。
  4. 分层 (Layer):处理 z-indextransform 等,生成图层树。
  5. 绘制 (Paint):生成绘制指令列表。
  6. 合成 (Composite):将图层切分成图块 (Tile),由 GPU 栅格化并在屏幕上显示。

在此过程中,如果遇到 <script>,可能会阻塞 DOM 解析(除非加了 deferasync),如果遇到 CSS,会阻塞渲染树的生成。


📝 总结

从 URL 输入到页面展示,这短短几秒钟,背后是无数行代码和多个进程的精密协作:

  1. 用户输入:主进程处理 URL,触发 beforeunload
  2. 网络请求:网络进程查 DNS,建 TCP,发 HTTP,处理重定向。
  3. 准备渲染:响应头返回,判断是 HTML,准备渲染进程。
  4. 提交文档IPC 通信的高潮。建立数据管道,主进程更新 UI 状态(地址栏、历史记录)。
  5. 页面渲染:渲染进程解析 HTML/CSS/JS,最终将像素绘制到屏幕上。

面试满分 Tips: 不要试图死记硬背每一个 TCP 标志位,而是要理解**“进程间是如何配合的”**。

  • 主进程是管家,负责协调。
  • 网络进程是快递员,负责取货。
  • 渲染进程是装修队,负责把货(代码)变成家(页面)。

希望这篇文章能帮你打通任督二脉,下次面试官问起,直接自信地甩出这张知识图谱!🚀

url.png

本文基于 Chromium 多进程架构原理解析,参考资料:Chromium Design Documents.