浏览器与网络高频面试题

24 阅读8分钟

浏览器与网络高频面试题

1. 从输入 URL 到页面显示发生了什么?⭐⭐⭐

这是最经典的综合题,考察对整个网络请求和浏览器渲染流程的理解。

完整流程(8 大步骤)

阶段步骤关键细节
① DNS 解析域名 → IP浏览器缓存 → OS 缓存 → hosts → 路由器 → ISP DNS → 根 → 顶级 → 权威
② TCP 连接三次握手SYN → SYN+ACK → ACK
③ TLS 握手HTTPS 才有证书验证 + 密钥协商(TLS 1.3 仅 1-RTT)
④ HTTP 请求发送请求报文请求行 + 请求头 + 请求体
⑤ 服务器响应返回资源状态码 + 响应头 + 响应体
⑥ 解析构建DOM + CSSOMHTML → DOM 树,CSS → CSSOM 树
⑦ 渲染绘制Layout → Paint → Composite布局 → 绘制 → 合成层
⑧ JS 执行可能触发回流重绘script 阻塞解析(async/defer 可优化)
TCP 三次握手(建立连接):
  客户端 → 服务器:SYN=1, seq=x          (我想连接)
  服务器 → 客户端:SYN=1, ACK=1, seq=y, ack=x+1  (好的,我准备好了)
  客户端 → 服务器:ACK=1, seq=x+1, ack=y+1       (收到,开始通信)

TCP 四次挥手(断开连接):
  客户端 → 服务器:FIN=1     (我发完了)
  服务器 → 客户端:ACK=1     (知道了,我可能还没发完)
  服务器 → 客户端:FIN=1     (我也发完了)
  客户端 → 服务器:ACK=1     (好的,等 2MSL 后关闭)
<!-- script 标签对解析的影响 -->
<script src="app.js"></script>          <!-- ❌ 阻塞 HTML 解析 -->
<script src="app.js" async></script>    <!-- ✅ 下载不阻塞,下载完立即执行(可能在DOMContentLoaded前后) -->
<script src="app.js" defer></script>    <!-- ✅ 下载不阻塞,DOMContentLoaded 前按顺序执行 -->

💡 面试加分点:三次握手是为了防止已失效的连接请求到达服务器;四次挥手中客户端要等 2MSL(最大报文段生存时间)是为了确保最后一个 ACK 能到达服务器。


2. HTTP 和 HTTPS 的区别?⭐⭐⭐

特性HTTPHTTPS
端口80443
安全性❌ 明文传输,可被窃听/篡改✅ SSL/TLS 加密传输
证书不需要需要 CA 颁发的数字证书
性能较快略慢(TLS 1.3 已大幅优化)
SEO权重低✅ Google/百度优先收录
数据完整性❌ 可被中间人篡改✅ 防篡改(MAC 校验)

TLS 1.2 握手流程(2-RTT)

1. Client Hello:支持的加密套件 + 随机数 A + 支持的 TLS 版本
2. Server Hello:选定的加密套件 + 随机数 B + 数字证书(含公钥)
3. 客户端验证证书(CA 链验证),生成预主密钥(Pre-Master Secret),用公钥加密发送
4. 双方用 随机数A + 随机数B + 预主密钥 → 生成会话密钥(对称密钥)
5. 后续通信使用对称加密(AES)→ 速度快

TLS 1.3 的改进

TLS 1.2:需要 2-RTT 握手
TLS 1.3:仅需 1-RTT 握手(甚至 0-RTT 恢复会话)

改进点:
- 移除不安全的加密算法(RSA 密钥交换、RC4、3DES 等)
- 握手过程从 2-RTT 优化到 1-RTT
- 支持 0-RTT 恢复(复用之前的会话密钥)
- 密钥交换只保留 ECDHE

💡 面试加分点:HTTPS 并非整个过程都用非对称加密,而是用非对称加密协商对称密钥,后续传输用对称加密(AES),因为对称加密速度快 100+ 倍。


3. HTTP/1.1、HTTP/2、HTTP/3 的区别?⭐⭐⭐

特性HTTP/1.1HTTP/2HTTP/3
底层协议TCPTCPQUIC(基于 UDP)
多路复用❌ 队头阻塞✅ 多路复用✅ 无队头阻塞
头部压缩✅ HPACK✅ QPACK
服务器推送✅ Server Push
传输格式文本✅ 二进制分帧✅ 二进制
连接建立TCP 三次握手TCP 三次握手✅ 0-RTT/1-RTT
队头阻塞应用层 + 传输层仅传输层✅ 彻底解决
HTTP/1.1 的问题:
├── 队头阻塞:一个请求卡住,后续请求排队等待
├── 头部冗余:每次都发送大量重复头部(Cookie等)
├── 文本传输:解析效率低
└── 只能客户端主动请求

HTTP/2 解决了应用层问题:
├── 多路复用:一个 TCP 连接并发多个流
├── HPACK 头部压缩:静态表 + 动态表 + 霍夫曼编码
├── 二进制分帧:Header帧 + Data帧
├── 服务器推送:主动推送关联资源
└── 流优先级:重要资源优先传输

HTTP/3 解决了传输层问题:
├── QUIC 协议:基于 UDP,内置 TLS 1.3
├── 0-RTT 连接:复用会话,首个请求就能带数据
├── 独立流:一个流丢包不影响其他流
└── 连接迁移:切换网络(WiFi4G)连接不断

💡 面试加分点:HTTP/2 虽然解决了应用层队头阻塞,但 TCP 层的队头阻塞仍然存在(一个 TCP 包丢失会阻塞所有流),HTTP/3 用 QUIC(UDP) 彻底解决了这个问题。


4. 常见的 HTTP 状态码?⭐⭐⭐

按分类记忆

分类范围含义
1xx信息响应请求已接收,继续处理
2xx成功请求已成功处理
3xx重定向需要进一步操作
4xx客户端错误请求有问题
5xx服务器错误服务器处理出错

高频状态码详解

状态码名称含义常见场景
200OK请求成功GET 获取数据成功
201Created资源创建成功POST 创建数据成功
204No Content成功但无内容返回DELETE 删除成功
206Partial Content部分内容断点续传/分片下载
301Moved Permanently永久重定向域名迁移,浏览器会缓存
302Found临时重定向短链接跳转
304Not Modified资源未修改,使用缓存协商缓存命中
307Temporary Redirect临时重定向(保持方法HSTS 跳转
308Permanent Redirect永久重定向(保持方法API 版本迁移
400Bad Request请求参数错误表单验证失败
401Unauthorized未认证Token 过期/未登录
403Forbidden无权限非管理员访问后台
404Not Found资源不存在URL 拼写错误
405Method Not Allowed方法不允许用 GET 调用 POST 接口
413Payload Too Large请求体过大上传文件超过限制
429Too Many Requests请求频率过高触发限流/反爬
500Internal Server Error服务器内部错误后端代码报错
502Bad Gateway网关错误Nginx 无法连接后端
503Service Unavailable服务不可用服务器维护/过载
504Gateway Timeout网关超时后端响应超时
❌ 常见混淆:
  301 vs 302301 浏览器会缓存跳转,302 每次都会请求原地址
  301 vs 308301 可能将 POST 改为 GET308 严格保持原方法
  401 vs 403401 是"你是谁?"(未认证),403 是"你不能进"(已认证但无权限)

💡 面试加分点304 的完整流程 → 浏览器发请求带 If-None-Match(ETag)或 If-Modified-Since → 服务器对比发现资源未变 → 返回 304(不带响应体)→ 浏览器使用本地缓存。


5. 浏览器缓存机制?⭐⭐⭐

缓存判断流程

浏览器请求资源
    │
    ├── 强缓存命中?(检查 Cache-Control / Expires)
    │   ├── ✅ 命中 → 直接使用缓存(200 from cache)
    │   └── ❌ 未命中 ↓
    │
    ├── 协商缓存?(发请求到服务器验证)
    │   ├── 服务器返回 304 → 使用本地缓存
    │   └── 服务器返回 200 → 使用新资源,更新缓存
    │
    └── 无缓存 → 正常请求资源

两种缓存机制对比

类型强缓存协商缓存
是否发请求❌ 不发请求✅ 发请求到服务器
HTTP 头(优先)Cache-Control: max-age=3600ETag / If-None-Match
HTTP 头(备选)Expires(绝对时间,已过时)Last-Modified / If-Modified-Since
状态码200 (from disk/memory cache)304 Not Modified
适用场景不常变化的静态资源需要验证是否更新的资源

实战缓存策略

# ✅ HTML 文件:不缓存或协商缓存(保证及时更新)
location ~* \.html$ {
    add_header Cache-Control "no-cache";  # 每次都协商验证
}

# ✅ JS/CSS 文件:强缓存 + 文件名 hash(内容变则文件名变)
location ~* \.(js|css)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# ✅ 图片/字体:强缓存
location ~* \.(png|jpg|gif|svg|woff2)$ {
    add_header Cache-Control "public, max-age=2592000";  # 30天
}

# ✅ API 接口:不缓存
location /api/ {
    add_header Cache-Control "no-store";
}
Cache-Control 常用指令速查:
┌──────────────────┬──────────────────────────────┐
│ no-store         │ 完全不缓存(连协商缓存都不要)    │
│ no-cache         │ 可以缓存,但每次都要向服务器验证   │
│ max-age=3600     │ 缓存有效期 3600 秒              │
│ public           │ 任何中间节点都可以缓存(CDN等)    │
│ private          │ 只有浏览器能缓存                  │
│ immutable        │ 有效期内不验证(配合hash使用)     │
│ s-maxage=3600    │ CDN 缓存有效期(覆盖 max-age)   │
│ must-revalidate  │ 过期后必须验证,不能用过期缓存     │
└──────────────────┴──────────────────────────────┘

💡 面试加分点ETagLast-Modified 更精确,因为 Last-Modified 只精确到秒,如果 1 秒内修改了多次文件,Last-Modified 无法感知;而且有些文件定期重新生成但内容不变,Last-Modified 变了但 ETag 不变。


6. 什么是跨域?如何解决?

同源策略:协议 + 域名 + 端口 完全相同才算同源。

// 解决方案

// 1. CORS(后端设置响应头)
// Access-Control-Allow-Origin: https://example.com
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: Content-Type, Authorization
// Access-Control-Allow-Credentials: true(允许携带 Cookie)

// 2. 代理服务器(开发环境)
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      }
    }
  }
}

// 3. JSONP(只支持 GET,已过时)
function jsonp(url, callback) {
  const script = document.createElement('script')
  const callbackName = `jsonp_${Date.now()}`
  window[callbackName] = (data) => {
    callback(data)
    delete window[callbackName]
    script.remove()
  }
  script.src = `${url}?callback=${callbackName}`
  document.head.appendChild(script)
}

// 4. postMessage(跨窗口通信)
// 发送
window.parent.postMessage({ type: 'login', data: userInfo }, 'https://parent.com')
// 接收
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://child.com') return
  console.log(e.data)
})

// 5. Nginx 反向代理(生产环境)
// location /api/ {
//   proxy_pass http://backend-server/;
//   proxy_set_header Host $host;
// }

7. XSS 和 CSRF 攻击是什么?如何防御?

// XSS(跨站脚本攻击):注入恶意脚本
// 攻击:<script>document.cookie</script> 注入到页面

// 防御:
// 1. 转义 HTML 特殊字符
const escapeHtml = (str) => str
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&#x27;')

// 2. CSP(内容安全策略)
// Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-xxx'

// 3. HttpOnly Cookie(JS 无法读取)
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

// CSRF(跨站请求伪造):利用用户已登录的身份发起请求
// 攻击:恶意网站发起请求到目标网站,携带用户 Cookie

// 防御:
// 1. CSRF Token(每次请求携带随机 token)
// 2. SameSite Cookie
// Set-Cookie: token=xxx; SameSite=Strict
// 3. 验证 Referer/Origin 请求头
// 4. 双重 Cookie 验证

8. 前端性能优化有哪些方法?

// 1. 减少 HTTP 请求
// - 合并 CSS/JS 文件
// - 使用雪碧图(HTTP/1.1)
// - 内联小图片(base64)

// 2. 资源压缩
// - Gzip/Brotli 压缩
// - 图片压缩(WebP 格式)
// - 代码压缩(minify)

// 3. 缓存策略(见上方)

// 4. 懒加载
// 图片懒加载
// <img loading="lazy" src="image.jpg" alt="..." />

// Intersection Observer 实现懒加载
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
})
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img))

// 5. 代码分割
// React:React.lazy + Suspense
// Vue:defineAsyncComponent
// Webpack:import() 动态导入

// 6. CDN 加速
// 将静态资源部署到 CDN,就近访问

// 7. 预加载/预连接
// <link rel="preload" href="critical.css" as="style" />
// <link rel="prefetch" href="next-page.js" />
// <link rel="preconnect" href="https://api.example.com" />
// <link rel="dns-prefetch" href="https://cdn.example.com" />

// 8. 减少重排重绘(见 HTML 部分)

// 9. Web Worker:将耗时计算移到后台线程
const worker = new Worker('worker.js')
worker.postMessage({ data: bigArray })
worker.onmessage = (e) => console.log('计算结果:', e.data)

9. 什么是 WebSocket?与 HTTP 的区别?

// WebSocket:全双工通信协议,服务器可以主动推送消息

// 建立连接
const ws = new WebSocket('wss://api.example.com/ws')

ws.onopen = () => {
  console.log('连接建立')
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }))
}

ws.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('收到消息:', data)
}

ws.onclose = (event) => {
  console.log('连接关闭:', event.code, event.reason)
  // 自动重连
  setTimeout(() => reconnect(), 3000)
}

ws.onerror = (error) => {
  console.error('连接错误:', error)
}

// 发送消息
ws.send(JSON.stringify({ type: 'message', content: 'Hello' }))

// 关闭连接
ws.close(1000, '正常关闭')

// HTTP vs WebSocket
// HTTP:请求-响应模式,单向,无状态
// WebSocket:全双工,持久连接,低延迟
// 适用场景:聊天室、实时通知、在线游戏、股票行情

10. 什么是 Service Worker?

// Service Worker:运行在浏览器后台的脚本,可以拦截网络请求、实现离线缓存

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW 注册成功:', reg.scope))
    .catch(err => console.error('SW 注册失败:', err))
}

// sw.js
const CACHE_NAME = 'my-app-v1'
const STATIC_ASSETS = ['/', '/index.html', '/app.js', '/style.css']

// 安装:缓存静态资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
  )
})

// 激活:清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key)))
    )
  )
})

// 拦截请求:缓存优先策略
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      if (cached) return cached  // 返回缓存
      return fetch(event.request).then(response => {
        const clone = response.clone()
        caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone))
        return response
      })
    })
  )
})

11. 什么是 CDN?工作原理是什么?

CDN(内容分发网络)工作原理:

1. 用户请求 cdn.example.com/image.jpg
2. DNS 解析返回最近的 CDN 节点 IP
3. 用户从最近的 CDN 节点获取资源
4. 若 CDN 节点没有缓存,则从源站拉取并缓存

优势:
- 就近访问,减少延迟
- 减轻源站压力
- 提高可用性(多节点冗余)
- 防御 DDoS 攻击

前端使用 CDN 的最佳实践:
- 静态资源(JS/CSS/图片)使用 CDN
- 文件名包含 hash(内容变化则 URL 变化,避免缓存问题)
- 设置长期缓存(max-age=31536000)
- 使用 SRI(子资源完整性)验证
<!-- SRI:防止 CDN 资源被篡改 -->
<script
  src="https://cdn.example.com/app.abc123.js"
  integrity="sha384-xxxxx"
  crossorigin="anonymous"
></script>

12. 什么是 RESTful API?

// REST 原则:
// 1. 无状态:每次请求包含所有必要信息
// 2. 统一接口:使用 HTTP 方法表示操作
// 3. 资源导向:URL 表示资源,不包含动词

// RESTful API 设计规范
// GET    /users          获取用户列表
// GET    /users/:id      获取单个用户
// POST   /users          创建用户
// PUT    /users/:id      完整更新用户
// PATCH  /users/:id      部分更新用户
// DELETE /users/:id      删除用户

// 嵌套资源
// GET    /users/:id/posts         获取用户的文章列表
// POST   /users/:id/posts         为用户创建文章
// DELETE /users/:id/posts/:postId 删除用户的某篇文章

// 查询参数
// GET /users?page=1&size=10&sort=createdAt&order=desc&status=active

// 响应格式
const response = {
  code: 0,
  message: 'success',
  data: {
    list: [],
    total: 100,
    page: 1,
    size: 10,
  }
}

// 错误响应
const errorResponse = {
  code: 400,
  message: '参数错误',
  errors: [
    { field: 'email', message: '邮箱格式不正确' }
  ]
}

13. 什么是 JWT(JSON Web Token)?

// JWT 结构:Header.Payload.Signature
// Header:算法和类型
// Payload:用户信息(不要存敏感数据)
// Signature:签名(防篡改)

// 示例 JWT
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJ1c2VySWQiOjEsIm5hbWUiOiLlvKDkuIkiLCJpYXQiOjE2MDAwMDAwMDAsImV4cCI6MTYwMDAwMzYwMH0.
// xxxxx

// 前端使用 JWT
const login = async (credentials) => {
  const { data } = await axios.post('/api/auth/login', credentials)
  localStorage.setItem('token', data.token)
  axios.defaults.headers.Authorization = `Bearer ${data.token}`
}

// 请求拦截器自动添加 token
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

// JWT 优缺点
// 优点:无状态、可扩展、跨域友好
// 缺点:无法主动失效(需要黑名单机制)、payload 可被解码(不要存敏感信息)

14. 什么是 OAuth 2.0?

OAuth 2.0 授权码流程(最安全):

1. 用户点击"使用微信登录"
2. 跳转到微信授权页面
3. 用户同意授权
4. 微信重定向回应用,携带 code
5. 应用后端用 code + client_secret 换取 access_token
6. 用 access_token 获取用户信息
7. 应用生成自己的 token 返回给前端

前端实现:
// 第一步:跳转到授权页面
const handleWechatLogin = () => {
  const params = new URLSearchParams({
    appid: 'wx_app_id',
    redirect_uri: encodeURIComponent('https://myapp.com/callback'),
    response_type: 'code',
    scope: 'snsapi_userinfo',
    state: generateRandomState(),  // 防 CSRF
  })
  window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?${params}`
}

// 第二步:处理回调
const handleCallback = async () => {
  const params = new URLSearchParams(window.location.search)
  const code = params.get('code')
  const state = params.get('state')

  // 验证 state 防 CSRF
  if (state !== sessionStorage.getItem('oauth_state')) {
    throw new Error('Invalid state')
  }

  // 发送 code 到后端换取 token
  const { data } = await axios.post('/api/auth/wechat', { code })
  localStorage.setItem('token', data.token)
}

15. 浏览器的渲染进程有哪些线程?

浏览器渲染进程包含以下线程:

1. GUI 渲染线程:
   - 解析 HTML/CSS,构建 DOM/CSSOM
   - 布局、绘制
   - 与 JS 引擎线程互斥(JS 执行时 GUI 暂停)

2. JS 引擎线程(V8):
   - 执行 JavaScript 代码
   - 与 GUI 渲染线程互斥

3. 事件触发线程:
   - 管理事件队列
   - 将事件回调放入任务队列

4. 定时器触发线程:
   - setTimeout/setInterval 计时
   - 计时完成后将回调放入任务队列

5. 异步 HTTP 请求线程:
   - 处理 XMLHttpRequest/fetch
   - 请求完成后将回调放入任务队列

这就是为什么 JS 执行时间过长会导致页面卡顿(GUI 渲染被阻塞)
// 解决 JS 阻塞渲染的方案

// 1. 将耗时任务分片(时间切片)
function processLargeArray(array, callback) {
  const chunkSize = 1000
  let index = 0

  function processChunk() {
    const end = Math.min(index + chunkSize, array.length)
    for (; index < end; index++) {
      callback(array[index])
    }
    if (index < array.length) {
      requestAnimationFrame(processChunk)  // 下一帧继续
    }
  }

  requestAnimationFrame(processChunk)
}

// 2. 使用 Web Worker 在后台线程处理
const worker = new Worker('heavy-task.js')
worker.postMessage(largeData)
worker.onmessage = (e) => console.log('处理完成:', e.data)

// 3. 使用 requestIdleCallback 在空闲时执行
requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    processTask(tasks.shift())
  }
})