跳到正文
前端知识库
面试真题

模块、网络与构建真题

详细整理 CommonJS/ESM、HTTP 版本、跨域安全、缓存、URL 访问链路、Webpack 构建与 Module Federation 的面试回答。

约 12 分钟CommonJS · ESM · HTTP · HTTPS · CORS · 缓存 · Webpack · Module Federation

模块、网络与构建真题

1. require 和 import 有什么区别?

先给结论:CommonJS 更偏运行时加载和对象导出,ES module 更偏静态分析和实时绑定。两者都能表达模块依赖,但加载时机、循环依赖和构建优化边界不同。

维度 CommonJS ES module
语法 require()、module.exports import、export
加载 通常执行到该语句时求值,可放在条件分支 静态 import 必须位于顶层,模块图可提前分析
导出语义 导出对象引用的当前结构;重新赋值需更新 module.exports 导出绑定是实时、只读的引用
顶层 this Node CommonJS 包装函数中的模块对象语境 ES module 顶层 this 为 undefined
优化 动态路径和可变导出会限制 Tree Shaking 静态结构更适合 Tree Shaking 和预加载
异步加载 require 本身同步(实现可有额外机制) import() 返回 Promise,适合代码分割
// counter.mjs
export let count = 0
export function increment() { count += 1 }

// main.mjs
import { count, increment } from './counter.mjs'
console.log(count) // 0
increment()
console.log(count) // 1,读取到实时绑定

面试中不要简单说“ESM 导出引用、CommonJS 导出拷贝”。CommonJS exports 与 module.exports 的关系、对象内部变更和重新赋值会改变观察结果;更稳妥的说法是:ESM 的命名导出是规范化的实时绑定,而 CommonJS 最终暴露的是运行时对象值。

1.1 循环依赖和兼容边界

模块只求值一次,但循环依赖可能在初始化完成前读取到未初始化绑定或部分导出对象。解决方式是减少环、把共享类型/常量提到低层模块,并避免在模块顶层执行依赖对方完整初始化的副作用。Node 的 ESM/CJS 互操作还受 package.json 的 type、exports、文件扩展名和运行时版本影响,应以目标环境实测。

深入阅读:模块加载器、ES6 Module。

2. HTTP/1.0、HTTP/1.1、HTTP/2 和 HTTP/3 怎么比较?

版本 关键能力 仍需注意
HTTP/1.0 常见实现按请求建立/关闭连接 握手开销大;并发和队头阻塞明显
HTTP/1.1 持久连接、Host、分块传输、更多方法 同一 TCP 连接的请求/响应顺序与 TCP 丢包仍会造成阻塞;管道化部署有限
HTTP/2 二进制分帧、stream 多路复用、HPACK 头部压缩 多路复用共享一条 TCP;丢包会造成连接级队头阻塞;服务端推送应谨慎
HTTP/3 QUIC/UDP 上的独立 stream、TLS 1.3 集成、连接迁移 需要服务器、CDN、代理和客户端协商;部署与观测链路不同

“HTTP/2 没有队头阻塞”是不完整的。它消除了 HTTP 层按请求排队的阻塞,但底层 TCP 丢包仍会阻塞同一连接;HTTP/3 才把传输层 stream 的阻塞进一步隔离。是否升级要结合丢包、连接复用、代理兼容和真实 RUM,而不是只看版本号。

深入阅读:HTTP 版本、性能网络优化。

3. 跨域请求如何定位?

同源由协议、主机和端口共同决定。CORS 不是“让服务器允许一切”,而是服务器通过响应头授予浏览器脚本读取跨源响应的权限。

  1. 简单请求可以直接发送,浏览器仍检查 Access-Control-Allow-Origin。
  2. 非简单请求通常先发 OPTIONS 预检,服务端需返回允许的方法和请求头。
  3. 携带 Cookie 或认证信息时,必须显式指定 origin,不能同时使用 Access-Control-Allow-Origin: * 和 Access-Control-Allow-Credentials: true。
  4. CORS 只改变浏览器读取权限,不替代服务端身份和资源授权。
// Vite 开发代理只改变本地浏览器看到的 origin
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.test',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
}

开发代理、Nginx 反向代理和 JSONP 的适用边界不同:代理让服务器之间跨域,生产仍需真实部署配置;JSONP 只能 GET 且响应会当脚本执行;mode: 'no-cors' 不能让脚本读取任意跨源响应。排障顺序应是 Network 面板看预检/实际请求、响应头、凭据和重定向,再确认反向代理是否改写了 Host、路径或 Cookie。

深入阅读:HTTP 真题补充、浏览器跨域速览。

3.1 为什么有的请求不预检,有的预检成功后仍失败?

常见免预检条件包括方法为 GET/HEAD/POST,且手动设置的头满足 CORS 安全列表;Content-Type 通常只能是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain,还需满足规范的值限制。POST application/json、PUT 或显式设置 Authorization 通常需要预检。浏览器还可能命中独立的预检缓存,因此 Network 中不是每次都能看到 OPTIONS。

预检携带 Origin、Access-Control-Request-Method,必要时还有 Access-Control-Request-Headers,用于询问后续请求是否允许。跨源预检通常不带实际请求的 Cookie,不能让“必须已经登录”的中间件先把它拒绝;放行预检也不代表放弃实际请求的身份和权限检查。

预检和实际响应都要返回正确的 CORS 头。动态按白名单回显 origin 时应附带 Vary: Origin,避免共享缓存把一个来源的响应授权错用于另一个来源;不要无条件回显任意 Origin。跨源 Cookie 请求还需客户端启用 credentials: 'include',并满足 Cookie 自身策略。需要读取非安全列表的响应头时,还要配置 Access-Control-Expose-Headers。

排障时区分“请求未发出”和“响应不可读”:简单请求可能已经改变服务端数据,只是浏览器不允许脚本读取响应,因此 CORS 失败不能作为操作没有发生的证据。postMessage 解决窗口间消息传递,WebSocket 使用自己的握手与鉴权规则,它们都不是普通跨源 fetch 的替代开关。

4. XSS、CSRF、SQL 注入分别怎么防?

风险 利用点 主要防线
XSS 不可信输入在页面上下文中被当成脚本/标记执行 上下文相关输出编码、框架默认转义、审计 innerHTML/eval、CSP、Cookie HttpOnly
CSRF 浏览器自动携带已有 Cookie,第三方诱导执行写操作 CSRF token、SameSite、校验 Origin/Referer、写操作不用 GET、关键操作再认证
SQL 注入 输入拼进 SQL 语句改变查询结构 参数化查询/预编译、白名单校验、最小权限数据库账号、审计和 WAF 辅助

XSS 与 CSRF 不是同一个问题:前者让脚本进入受信任页面,后者借用受害者已有身份发请求;有 XSS 时,很多前端 CSRF 防护也可能被读取或绕过。文件名、MIME 和前端过滤只能改善体验,不能替代服务端校验。

定位 XSS 时要追踪不可信数据从哪里进入、最终写入哪个危险位置。存储型常见于保存后展示的评论,反射型常见于把 URL 参数直接拼进响应;DOM 型强调客户端代码把 location、消息等数据写入 innerHTML 等危险位置。存储/反射描述数据到达方式,DOM 描述客户端执行路径,分类可以重叠;不能用一次输入过滤覆盖所有输出上下文。

参数绑定保护的是 SQL 的数据值,动态表名、列名和排序方向应从服务端白名单选取。下面以 mysql2/promise 的 execute 接口为例:

async function listUsers(db, { status, sort = 'created' }) {
  const columns = new Map([
    ['created', 'created_at'],
    ['name', 'display_name'],
  ]);
  if (typeof status !== 'string' || !columns.has(sort)) {
    throw new TypeError('Invalid query');
  }
  const sql = `SELECT id, display_name FROM users WHERE status = ? ORDER BY ${columns.get(sort)} DESC LIMIT 100`;
  const [rows] = await db.execute(sql, [status]);
  return rows;
}

即使 status 包含引号或 SQL 关键字,它也只是参数值;sort 的输入不会直接拼入 SQL。ORM 的原始查询、动态排序和拼接片段同样可能被注入,使用 ORM/WAF 不能替代这层约束。验证应包含恶意输入、非法排序键和数据库权限边界。

5. Cookie、缓存和鉴权如何串起来?

会话 Cookie 通过 Set-Cookie 下发、后续请求用 Cookie 回传;缓存头控制资源是否新鲜,两条机制不要混淆。

  • Secure:只通过 HTTPS 发送。
  • HttpOnly:脚本不能通过 document.cookie 读取。
  • SameSite=Lax/Strict/None:控制跨站携带行为;None 必须配 Secure。
  • Cache-Control: no-store:不保留副本;no-cache:可以存储但每次需要验证;private/public:限制共享缓存范围。
  • ETag/If-None-Match 和 Last-Modified/If-Modified-Since 是协商缓存,命中返回 304,不是业务成功码。

静态带 hash 的 JS/CSS 可以长时间强缓存;HTML、接口响应和用户私有数据应设置合适的缓存键与 Vary,不能把带身份的响应放入公共 CDN。Token 放在哪里要看威胁模型:受控 Cookie 能降低脚本直接读取风险,但仍需 CSRF 防护;localStorage 易于使用但 XSS 后可被读取。最终授权永远在服务端完成。

public 允许共享缓存参与,仍要满足缓存条件,并非“无条件缓存”;没有该指令也不能一概视为公开可缓存。ETag 是服务端生成的不透明验证器,不要求必须是内容哈希。对普通 GET/HEAD,存在 If-None-Match 时优先按它验证;仅使用 If-Modified-Since 时,判断资源是否在给定时间之后修改,不是简单比较时间字符串是否相等。

深入阅读:HTTP 缓存与头部、JWT 鉴权、浏览器存储与安全。

5.1 SameSite 判断的是站点,不是 origin

现代浏览器通常按协议和可注册域判断同站;origin 还比较完整主机名和端口。例如 https://app.example.com 与 https://api.example.com 跨源但同站:脚本读取响应需要 CORS,Cookie 是否匹配还要分别看 Domain、Path、Secure 和凭据模式。

属性 跨站请求携带 Cookie 的典型规则
Strict 跨站请求不携带,包括从外站链接进入
显式 Lax 通常只允许顶层导航且使用安全方法,例如点击 GET 链接;跨站 iframe、图片和 fetch 不因此获准
None; Secure 不施加 SameSite 限制,但仍受第三方 Cookie 策略、分区及其他 Cookie 条件约束

省略 SameSite 的浏览器默认行为可能带有短期兼容例外,不应把它当作显式 Lax 的保证。HTTPS 保护传输,不能阻止 CSRF;HttpOnly 阻止脚本读取 Cookie,却不阻止浏览器发送它。只依靠 SameSite 也不能防住被控制的同站子域,写操作仍需 CSRF token、来源和权限校验。

5.2 Basic、Session、JWT 和 OAuth 不是同一层概念

方案 核心对象 关键边界
HTTP Basic Authorization: Basic base64(user:password);常由 401 + WWW-Authenticate 发起挑战 Base64 是编码,应使用 TLS;每次发送凭证,退出与撤销体验有限
Session + Cookie 服务端保存会话,客户端携带随机 session ID 服务端可撤销;多实例需共享会话存储或明确的路由策略,不要求每台机器复制全部会话
JWT 带 claims 的令牌格式;常见形式是 JWS 签名令牌 签名不等于加密,载荷通常可读;需校验算法、签名、有效期、签发者和受众,撤销需额外设计
OAuth 2.0 让客户端获得访问资源的授权协议 access token 不一定是 JWT;需要登录身份时使用 OIDC 等明确的身份协议

Cookie 是传输和存储机制,JWT 是令牌格式,两者可以组合;原生 App 也可以通过 HTTP 客户端管理 Cookie。选择 Session 或 JWT 时比较撤销需求、令牌大小、验签成本、会话存储与缓存,而不是断言 JWT 天生更快或更慢。

以有 BFF 的授权码流程为例:

  1. BFF 为本次登录生成并保存 state、PKCE verifier,跳转授权端点时发送按 S256 计算的 challenge、明确的 redirect URI 和最小 scope。
  2. 用户在授权服务器完成认证和授权,浏览器带一次性 code 和 state 回到回调地址。
  3. BFF 验证 state、回调会话和预期地址,再携带 code、verifier 到 token 端点换取 token;客户端密钥只留在服务端。
  4. 使用 OIDC 时按协议校验 ID token,包括签名、issuer、audience、有效期和请求中使用的 nonce;不要把任意 access token 解码后的字段当作登录身份。
  5. BFF 建立自己的会话,给浏览器下发受保护的 session Cookie。访问资源时使用正确受众和 scope 的 access token;退出时处理本地会话、刷新令牌撤销和过期。

纯 SPA 是公开客户端,不能保守客户端密钥,也不应把密钥放进前端环境变量;使用授权码与 PKCE 时仍需处理 XSS、token 存储和刷新策略。流程中的 state、PKCE 和 OIDC nonce 各有职责,不能因“已有 HTTPS”而省略对应校验。

6. 地址栏输入 URL 后发生什么?

解析 URL
  -> 浏览器/系统/hosts/DNS 缓存
  -> 递归 DNS、CNAME/CDN 调度
  -> TCP 三次握手(HTTP/3 为 QUIC 建连)
  -> HTTPS 的 TLS 握手和证书校验
  -> HTTP 请求/响应
  -> 解析 HTML、CSS、JS,继续请求子资源
  -> DOM + CSSOM -> 样式计算 -> 布局 -> 绘制 -> 合成
  -> 交互触发脚本、请求和局部更新

回答时要补几个边界:DNS 可能命中浏览器、系统或 hosts;CDN 命中时不一定回源;HTTPS 的证书校验失败不能通过“忽略错误”解决;HTML 解析遇到阻塞脚本可能暂停,defer/async 改变执行时机;渲染和后续资源请求会交错进行。性能排查要把 DNS、连接、TTFB、下载、解析执行和绘制分别测量。

深入阅读:URL 与 DNS、浏览器渲染流水线、性能指标诊断。

7. Webpack 构建流程怎么讲?

配置/插件初始化
  -> Compiler
  -> entry 解析
  -> Module + Loader 递归构建依赖图
  -> seal / 优化 / Tree Shaking / splitChunks
  -> Chunk 和 Asset 生成
  -> emit/processAssets 写入产物

五个对象要分清:Compiler 管整个构建进程,Compilation 表示一次构建,Module 是源码/资源模块,Chunk 是按入口和异步边界组织的一组模块,Bundle/Asset 是最终文件。Loader 是模块级函数,Plugin 通过 hooks 参与全局流程。

常见追问:

  • splitChunks 应按路由、公共依赖、更新频率和缓存收益切分,过细会增加请求和解析成本。
  • Tree Shaking 依赖 ESM 静态结构和正确的 sideEffects 声明;动态 CommonJS、错误标记和全局注册会破坏结果。
  • CSS 提取、Terser、source map、缓存和构建 stats 都属于交付链路,不只是“打包成功”。
  • HMR 通过 hash/manifest 和 hot-update chunk 更新可接受模块;没有 accept 边界时会整页刷新。

深入阅读:Webpack 扩展与生命周期、Webpack 构建流程。

8. fullhash、chunkhash、contenthash 怎么选?

Hash 变化范围 缓存含义
fullhash 一次构建整体共享,任意模块变化可能使所有产物变 简单但缓存失效范围大
chunkhash 同一 Chunk 内共享,Chunk 内容变化会一起变 适合旧式分 chunk,但公共运行时代码可能牵连变化
contenthash 按单个资源内容计算 最适合长期缓存,需处理运行时和 CSS/JS 依赖拆分

hash 不是越细越好:文件名变化会影响 HTML 引用、CDN 清理、source map 和回滚;应配合稳定模块 ID、runtimeChunk、构建可重复性和缓存命中率验证。

9. Module Federation 解决什么问题?

Module Federation 允许一个构建在运行时消费另一个构建暴露的模块,适合独立发布的微前端或跨团队共享功能。它并不自动解决版本、权限、网络和运行时隔离:

  1. 远程入口不可用时要有超时、降级 UI 或旧版本回退。
  2. React/Vue 等 shared 依赖要明确 singleton、版本范围和初始化顺序,避免多个运行时导致状态/Hook 错误。
  3. 远程代码仍是供应链输入,应做签名/来源校验、CSP、审计和灰度;不能把任意 URL 当可信模块。
  4. 远程模块的 CSS、路由、事件和全局副作用要有命名空间与卸载清理。

面试中把它和 iframe、qiankun、npm 包作对比:Federation 偏运行时模块共享,iframe 隔离更强但通信成本高,npm 偏构建时复用;选型取决于独立部署、隔离、性能和团队边界。

10. 口述练习

任选一题,用“结论 -> 机制 -> 约束 -> 失败 -> 验证”回答:

  1. HTTP/2 仍然卡顿,你先看哪些证据?
  2. 本地代理成功但线上 CORS 失败,差异可能在哪里?
  3. 修改一个组件后所有 chunk hash 都变了,如何定位?
  4. 远程微前端加载失败时,如何保证主应用可用?

来源:高频真题解析与9月考点预测上.pdf、高频真题解析与9月考点预测下.pdf。宣传页和外部文章链接未作为结论;协议版本和构建行为按当前浏览器/工具边界补充。