浏览器与网络高频面试题

浏览器与网络高频面试题

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

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

完整流程(8 大步骤)

阶段 步骤 关键细节
① DNS 解析 域名 → IP 浏览器缓存 → OS 缓存 → hosts → 路由器 → ISP DNS → 根 → 顶级 → 权威
② TCP 连接 三次握手 SYN → SYN+ACK → ACK
③ TLS 握手 HTTPS 才有 证书验证 + 密钥协商(TLS 1.3 仅 1-RTT)
④ HTTP 请求 发送请求报文 请求行 + 请求头 + 请求体
⑤ 服务器响应 返回资源 状态码 + 响应头 + 响应体
⑥ 解析构建 DOM + CSSOM HTML → DOM 树,CSS → CSSOM 树
⑦ 渲染绘制 Layout → Paint → Composite 布局 → 绘制 → 合成层
⑧ JS 执行 可能触发回流重绘 script 阻塞解析(async/defer 可优化)
ini 复制代码
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 后关闭)
html 复制代码
<!-- 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 的区别?⭐⭐⭐

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

TLS 1.2 握手流程(2-RTT)

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

TLS 1.3 的改进

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

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

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

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


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

按分类记忆

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

高频状态码详解

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

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


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

缓存判断流程

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

两种缓存机制对比

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

实战缓存策略

nginx 复制代码
# ✅ 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";
}
arduino 复制代码
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. 什么是跨域?如何解决?

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

javascript 复制代码
// 解决方案

// 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 攻击是什么?如何防御?

javascript 复制代码
// 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. 前端性能优化有哪些方法?

javascript 复制代码
// 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 的区别?

javascript 复制代码
// 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?

javascript 复制代码
// 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?工作原理是什么?

markdown 复制代码
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(子资源完整性)验证
html 复制代码
<!-- SRI:防止 CDN 资源被篡改 -->
<script
  src="https://cdn.example.com/app.abc123.js"
  integrity="sha384-xxxxx"
  crossorigin="anonymous"
></script>

12. 什么是 RESTful API?

javascript 复制代码
// 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)?

javascript 复制代码
// 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?

markdown 复制代码
OAuth 2.0 授权码流程(最安全):

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

前端实现:
javascript 复制代码
// 第一步:跳转到授权页面
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. 浏览器的渲染进程有哪些线程?

markdown 复制代码
浏览器渲染进程包含以下线程:

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

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

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

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

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

这就是为什么 JS 执行时间过长会导致页面卡顿(GUI 渲染被阻塞)
javascript 复制代码
// 解决 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())
  }
})
相关推荐
weixin_4614085811 小时前
npm npx yarn
开发语言·前端·javascript
四千岁11 小时前
RAG系统中的分块
前端·javascript·后端
START_GAME12 小时前
MSSQL$SQL2016
java·服务器·前端
Captaincc12 小时前
Show me your works & token -稀土掘金上线内测作品广场和用量统计
前端·掘金社区·vibecoding
进击的明明12 小时前
闭包:JavaScript里的“随身背包” 🎒
前端·javascript·面试
紫幽13 小时前
从 0 用 Vue 做屏并生成 LVGL 单片机代码(完整源码备忘)
前端·单片机
众人皆醒我独醉13 小时前
源码导读:一张地图看懂 KServe 仓库
面试·云计算·gpu
郭邯13 小时前
用 AI 辅助开发一个文件大小转换工具:从需求到上线的完整过程
前端
勾勾圈圈蛋蛋13 小时前
前中台项目
前端
触底反弹13 小时前
🔐 前端鉴权不再难!手把手带你搞懂 JWT + Zustand + Axios 的完整登录方案
前端·axios