返回首页

说说地址栏输入 URL 敲下回车后发生了什么

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对浏览器工作原理的整体理解
  • 掌握从 URL 输入到页面展示的完整流程
  • 了解网络请求、资源加载、渲染引擎等关键环节
  • 能够分析性能瓶颈和优化点
  • 理解各阶段的技术细节和相互关系

核心概念(基础知识点)

完整流程概览

┌─────────────────────────────────────────────────────────────┐
│              从输入 URL 到页面展示的完整流程                   │
├─────────────────────────────────────────────────────────────┤
│  1. URL 解析                                                 │
│     - 判断输入类型(URL 或搜索关键词)                         │
│     - 协议补全、编码处理                                       │
├─────────────────────────────────────────────────────────────┤
│  2. DNS 查询                                                 │
│     - 浏览器缓存 → OS 缓存 → hosts → DNS 服务器               │
│     - 获取目标 IP 地址                                        │
├─────────────────────────────────────────────────────────────┤
│  3. 建立 TCP 连接                                            │
│     - 三次握手建立连接                                        │
│     - HTTPS 还需 TLS 握手                                     │
├─────────────────────────────────────────────────────────────┤
│  4. 发送 HTTP 请求                                           │
│     - 构建请求行、请求头、请求体                               │
│     - 发送请求并等待响应                                      │
├─────────────────────────────────────────────────────────────┤
│  5. 服务器处理请求                                           │
│     - 解析请求、路由匹配                                       │
│     - 执行业务逻辑、查询数据库                                  │
│     - 返回 HTTP 响应                                          │
├─────────────────────────────────────────────────────────────┤
│  6. 浏览器接收响应                                           │
│     - 解析响应头、状态码                                       │
│     - 处理重定向、缓存                                         │
│     - 接收响应体                                              │
├─────────────────────────────────────────────────────────────┤
│  7. 页面渲染                                                 │
│     - 解析 HTML 构建 DOM 树                                   │
│     - 解析 CSS 构建 CSSOM 树                                  │
│     - 合并为 Render Tree                                      │
│     - 布局(Layout)和绘制(Paint)                            │
└─────────────────────────────────────────────────────────────┘

关键性能指标

指标 说明 优化目标
TTFB Time To First Byte,首字节时间 < 200ms
FP First Paint,首次绘制 < 1s
FCP First Contentful Paint,首次内容绘制 < 1.8s
LCP Largest Contentful Paint,最大内容绘制 < 2.5s
TTI Time To Interactive,可交互时间 < 3.8s

详细解答(代码示例)

1. URL 解析

// URL 解析过程示意
function parseURL(input) {
  let url = input.trim();

  // 1. 判断是否为搜索关键词
  if (!isValidURL(url) && !url.includes(' ') && !url.includes('.')) {
    // 使用默认搜索引擎
    url = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
  }

  // 2. 补全协议
  if (!url.startsWith('http://') && !url.startsWith('https://')) {
    url = 'https://' + url;
  }

  // 3. 解析 URL 组成部分
  const urlObj = new URL(url);

  return {
    href: urlObj.href,        // 完整 URL
    protocol: urlObj.protocol, // 协议: https:
    host: urlObj.host,        // 主机: example.com:443
    hostname: urlObj.hostname, // 主机名: example.com
    port: urlObj.port,        // 端口: 443
    pathname: urlObj.pathname, // 路径: /path/to/page
    search: urlObj.search,    // 查询: ?query=value
    hash: urlObj.hash         // 锚点: #section
  };
}

// HSTS(HTTP Strict Transport Security)检查
// 如果域名在 HSTS 列表中,强制使用 HTTPS
const hstsList = ['google.com', 'github.com', 'example.com'];

function checkHSTS(hostname) {
  return hstsList.some(domain => hostname.endsWith(domain));
}

2. DNS 查询详解

// DNS 查询流程(简化示意)
async function resolveDNS(hostname) {
  // 1. 浏览器缓存检查
  const browserCache = checkBrowserCache(hostname);
  if (browserCache) return browserCache;

  // 2. 操作系统缓存检查
  const osCache = checkOSCache(hostname);
  if (osCache) return osCache;

  // 3. hosts 文件检查
  const hostsEntry = checkHostsFile(hostname);
  if (hostsEntry) return hostsEntry;

  // 4. DNS 服务器查询(递归查询)
  const dnsServers = ['8.8.8.8', '114.114.114.114'];
  for (const dnsServer of dnsServers) {
    try {
      const ip = await queryDNS(hostname, dnsServer);
      cacheDNSResult(hostname, ip);
      return ip;
    } catch (e) {
      continue;
    }
  }

  throw new Error('DNS resolution failed');
}

// DNS 预解析优化
<link rel="dns-prefetch" href="//api.example.com">
<link rel="preconnect" href="//api.example.com" crossorigin>

3. TCP 三次握手

客户端                              服务器
  |                                   |
  |-------- 1. SYN (seq=x) -------->|
  |  请求建立连接,发送序列号 x        |
  |                                   |
  |<------- 2. SYN-ACK (seq=y, ack=x+1) |
  |  同意建立连接,发送序列号 y        |
  |  确认收到客户端序列号 x+1          |
  |                                   |
  |-------- 3. ACK (ack=y+1) ------->|
  |  确认收到服务器序列号 y+1          |
  |                                   |
  |======== TCP 连接建立完成 ==========|

关键参数:
- seq: 序列号,用于数据排序和确认
- ack: 确认号,期望收到的下一个序列号
- SYN: 同步标志,用于建立连接
- ACK: 确认标志
// Node.js TCP 连接示例
const net = require('net');

const client = new net.Socket();

client.connect(80, 'example.com', () => {
  console.log('TCP 连接已建立');

  // 发送 HTTP 请求
  client.write('GET / HTTP/1.1\r\n');
  client.write('Host: example.com\r\n');
  client.write('\r\n');
});

client.on('data', (data) => {
  console.log('收到数据:', data.toString());
  client.destroy();
});

client.on('close', () => {
  console.log('连接已关闭');
});

4. HTTPS TLS 握手

HTTPS 额外步骤(TLS 握手):

客户端                              服务器
  |                                   |
  |-------- Client Hello ---------->|
  |  支持的 TLS 版本、加密套件列表     |
  |  客户端随机数                      |
  |                                   |
  |<------- Server Hello ------------|
  |  选定的 TLS 版本、加密套件         |
  |  服务器证书(含公钥)              |
  |  服务器随机数                      |
  |                                   |
  |-------- 验证证书 ---------------->|
  |  检查证书链、有效期、域名匹配      |
  |                                   |
  |-------- 密钥交换 ---------------->|
  |  生成预主密钥,用公钥加密发送      |
  |                                   |
  |<------- 加密通信开始 --------------|
  |  双方生成会话密钥,对称加密通信    |

TLS 1.3 优化:
- 握手从 2-RTT 减少到 1-RTT
- 支持 0-RTT 会话恢复

5. HTTP 请求与响应

// 请求报文
GET /api/users HTTP/1.1
Host: api.example.com
Connection: keep-alive
Accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=abc123; user=john
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...

// 请求体(GET 通常为空)


// 响应报文
HTTP/1.1 200 OK
Date: Mon, 18 Mar 2024 08:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 1234
Connection: keep-alive
Cache-Control: max-age=3600
ETag: "abc123"
Set-Cookie: session=xyz789; Path=/; HttpOnly; Secure

{"users": [{"id": 1, "name": "张三"}, ...]}
// 使用 Fetch API 发送请求
async function fetchData() {
  const response = await fetch('https://api.example.com/users', {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'Authorization': 'Bearer token123'
    },
    // 其他选项
    mode: 'cors',
    cache: 'default',
    credentials: 'include'
  });

  // 检查响应状态
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  // 解析响应体
  const data = await response.json();
  return data;
}

6. 页面渲染流程

// 渲染流程示意(简化)
class Renderer {
  constructor() {
    this.domTree = null;
    this.cssomTree = null;
    this.renderTree = null;
  }

  // 1. 解析 HTML 构建 DOM
  parseHTML(html) {
    const parser = new DOMParser();
    this.domTree = parser.parseFromString(html, 'text/html');
    return this.domTree;
  }

  // 2. 解析 CSS 构建 CSSOM
  parseCSS(css) {
    // CSS 解析器将 CSS 转换为对象模型
    this.cssomTree = this.parseCSSRules(css);
    return this.cssomTree;
  }

  // 3. 合并 DOM 和 CSSOM 构建 Render Tree
  buildRenderTree() {
    this.renderTree = this.combineTrees(this.domTree, this.cssomTree);
    return this.renderTree;
  }

  // 4. 布局(Layout/Reflow)
  layout() {
    // 计算每个节点的几何信息(位置、大小)
    this.calculateLayout(this.renderTree);
  }

  // 5. 绘制(Paint)
  paint() {
    // 将渲染树绘制到屏幕上
    this.drawToScreen(this.renderTree);
  }

  // 6. 合成(Composite)
  composite() {
    // 将多个图层合成为最终页面
    this.compositeLayers();
  }
}

深入理解(原理剖析)

浏览器多进程架构

Chrome 多进程架构:

┌─────────────────────────────────────────────────────────────┐
│                    浏览器主进程(Browser Process)            │
│  - 地址栏、书签栏、前进后退按钮                               │
│  - 网络请求管理                                              │
│  - 文件访问权限                                              │
├─────────────────────────────────────────────────────────────┤
│                    GPU 进程(GPU Process)                    │
│  - 3D CSS 渲染                                               │
│  - Canvas 绘制                                               │
│  - 视频解码                                                  │
├─────────────────────────────────────────────────────────────┤
│                    插件进程(Plugin Process)                 │
│  - Flash、PDF 查看器等                                       │
├─────────────────────────────────────────────────────────────┤
│                    渲染进程(Renderer Process)× N            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │  Tab 1      │  │  Tab 2      │  │  Tab 3      │          │
│  │  (站点 A)   │  │  (站点 B)   │  │  (站点 C)   │          │
│  │             │  │             │  │             │          │
│  │ ┌─────────┐ │  │ ┌─────────┐ │  │ ┌─────────┐ │          │
│  │ │ Blink   │ │  │ │ Blink   │ │  │ │ Blink   │ │          │
│  │ │ 渲染引擎 │ │  │ │ 渲染引擎 │ │  │ │ 渲染引擎 │ │          │
│  │ │ ┌─────┐ │ │  │ │ ┌─────┐ │ │  │ │ ┌─────┐ │ │          │
│  │ │ │ V8  │ │ │  │ │ │ V8  │ │ │  │ │ │ V8  │ │ │          │
│  │ │ │ JS  │ │ │  │ │ │ JS  │ │ │  │ │ │ JS  │ │ │          │
│  │ │ └─────┘ │ │  │ │ └─────┘ │ │  │ │ └─────┘ │ │          │
│  │ └─────────┘ │  │ └─────────┘ │  │ └─────────┘ │          │
│  └─────────────┘  └─────────────┘  └─────────────┘          │
└─────────────────────────────────────────────────────────────┘

站点隔离(Site Isolation):
- 不同站点使用不同渲染进程
- 防止恶意站点访问其他站点的数据
- 增加内存使用,但提高安全性

关键渲染路径(Critical Rendering Path)

关键渲染路径优化:

┌─────────────────────────────────────────────────────────────┐
│  关键资源(阻塞渲染)                                         │
│  ├── HTML:必须下载并解析                                     │
│  ├── CSS:阻塞渲染,需要构建 CSSOM                            │
│  └── JavaScript:默认阻塞解析(除非 async/defer)              │
├─────────────────────────────────────────────────────────────┤
│  优化策略:                                                   │
│  1. 减少关键资源数量                                          │
│     - 内联关键 CSS                                            │
│     - 延迟加载非关键 JS                                       │
│                                                              │
│  2. 减少关键资源大小                                          │
│     - 压缩(Gzip/Brotli)                                     │
│     - 代码分割                                                │
│     - Tree Shaking                                            │
│                                                              │
│  3. 优化关键路径长度                                          │
│     - 预加载关键资源                                          │
│     - 使用 CDN                                                │
│     - HTTP/2 服务器推送                                       │
└─────────────────────────────────────────────────────────────┘

渲染阻塞示例:
<html>
  <head>
    <!-- 阻塞渲染 -->
    <link rel="stylesheet" href="large.css">

    <!-- 阻塞解析 -->
    <script src="app.js"></script>

    <!-- 优化:异步加载 -->
    <script src="analytics.js" async></script>

    <!-- 优化:延迟执行 -->
    <script src="app.js" defer></script>
  </head>
</html>

重排(Reflow)与重绘(Repaint)

// 触发重排的操作(性能开销大)
function triggerReflow() {
  // 读取布局属性(强制同步布局)
  const width = element.offsetWidth;

  // 修改布局属性
  element.style.width = '100px';
  element.style.height = '100px';
  element.style.margin = '10px';

  // 添加/删除元素
  document.body.appendChild(newElement);

  // 修改字体大小
  element.style.fontSize = '16px';
}

// 触发重绘的操作(性能开销较小)
function triggerRepaint() {
  element.style.color = 'red';
  element.style.backgroundColor = 'blue';
  element.style.visibility = 'hidden';
  element.style.borderRadius = '5px';
}

// 优化:批量修改样式
function optimizeReflow() {
  // 方法 1:使用 CSS 类
  element.classList.add('new-styles');

  // 方法 2:使用 CSSText
  element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';

  // 方法 3:先隐藏再修改
  element.style.display = 'none';
  // ... 批量修改 ...
  element.style.display = 'block';

  // 方法 4:使用 DocumentFragment
  const fragment = document.createDocumentFragment();
  for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    fragment.appendChild(div);
  }
  document.body.appendChild(fragment);

  // 方法 5:使用 requestAnimationFrame
  requestAnimationFrame(() => {
    // 在下一帧统一修改
    element.style.width = '100px';
  });
}

浏览器缓存机制

缓存策略决策树:

┌─────────────────────────────────────────────────────────────┐
  浏览器请求资源                                              
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  1. Service Worker 缓存?                                    
       返回 SW 缓存                                        
       继续                                                
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  2. 内存缓存(Memory Cache)?                               
       返回内存缓存(快,但容量小)                          
       继续                                                
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  3. 磁盘缓存(Disk Cache/HTTP Cache)?                      
       根据缓存策略决定是否使用                              
       继续                                                
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  4. 发送网络请求                                             
     - 协商缓存(304 Not Modified)                            
     - 或返回新资源(200)                                     
└─────────────────────────────────────────────────────────────┘

缓存控制头部:
Cache-Control: no-cache        // 强制重新验证
Cache-Control: no-store        // 不缓存
Cache-Control: max-age=3600    // 缓存 1 小时
Cache-Control: private         // 仅浏览器缓存
Cache-Control: public          // 可被中间代理缓存
Expires: Wed, 21 Oct 2025 07:28:00 GMT  // 过期时间
ETag: "abc123"                 // 资源标识
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

最佳实践

1. 前端性能优化

<!-- 1. 资源预加载 -->
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//api.example.com">

<!-- 预连接(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- 预加载关键资源 -->
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>

<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.html">

<!-- 2. 关键 CSS 内联 -->
<style>
  /* 首屏关键 CSS */
  .hero { ... }
  .header { ... }
  .nav { ... }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="/css/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

<!-- 3. JavaScript 加载优化 -->
<!-- async: 异步加载,加载完立即执行 -->
<script src="analytics.js" async></script>

<!-- defer: 异步加载,DOM 解析完成后执行 -->
<script src="app.js" defer></script>

<!-- type="module": 自动 defer -->
<script type="module" src="app.mjs"></script>

<!-- 4. 图片优化 -->
<img src="photo.jpg"
     srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
     sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
     loading="lazy"
     alt="描述">

<!-- 5. 使用 Service Worker 缓存 -->
<script>
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js');
  }
</script>

2. 服务端优化

# Nginx 性能优化配置
server {
    listen 443 ssl http2;

    # 启用 Gzip/Brotli 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript;

    # 浏览器缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary Accept-Encoding;
    }

    # HTML 不缓存
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-cache";
    }

    # 启用 HTTP/2 Server Push
    location = /index.html {
        http2_push /css/critical.css;
        http2_push /js/app.js;
    }
}

3. 性能监控

// 使用 Performance API 监控性能
window.addEventListener('load', () => {
  // Navigation Timing
  const timing = performance.timing;

  const metrics = {
    // DNS 查询时间
    dns: timing.domainLookupEnd - timing.domainLookupStart,

    // TCP 连接时间
    tcp: timing.connectEnd - timing.connectStart,

    // SSL 握手时间
    ssl: timing.connectEnd - timing.secureConnectionStart,

    // 首字节时间(TTFB)
    ttfb: timing.responseStart - timing.requestStart,

    // DOM 解析时间
    domParse: timing.domComplete - timing.domLoading,

    // 资源加载时间
    resourceLoad: timing.loadEventStart - timing.domContentLoadedEventEnd,

    // 总加载时间
    total: timing.loadEventEnd - timing.navigationStart
  };

  console.log('性能指标:', metrics);

  // 上报到监控系统
  reportMetrics(metrics);
});

// 使用 Performance Observer 监控 LCP、FID、CLS
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('性能条目:', entry.name, entry.startTime, entry.duration);

    // LCP (Largest Contentful Paint)
    if (entry.entryType === 'largest-contentful-paint') {
      console.log('LCP:', entry.renderTime || entry.loadTime);
    }

    // FID (First Input Delay)
    if (entry.entryType === 'first-input') {
      console.log('FID:', entry.processingStart - entry.startTime);
    }
  }
});

observer.observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });

面试要点

  1. 完整流程记忆要点

    • URL 解析 → DNS 查询 → TCP 连接 → HTTP 请求 → 服务器处理 → 浏览器渲染
    • 每个阶段的关键技术和优化点
  2. DNS 优化

    • DNS 预解析(dns-prefetch)
    • 预连接(preconnect)
    • 减少域名数量
    • 使用 DNS 缓存
  3. TCP/HTTP 优化

    • 使用 HTTP/2 或 HTTP/3
    • 启用 Keep-Alive
    • 使用 CDN
    • 压缩资源
  4. 渲染优化

    • 减少关键资源数量
    • 延迟加载非关键资源
    • 避免渲染阻塞
    • 优化重排重绘
  5. 面试高频问题

    • 从输入 URL 到页面展示发生了什么?(按阶段详细回答)
    • 什么是重排和重绘?如何优化?
    • 浏览器缓存机制?
    • 什么是 CRP(Critical Rendering Path)?
    • 如何优化首屏加载速度?
    • 什么是 TTFB?如何优化?
    • Service Worker 的作用?