模块、网络与构建真题
详细整理 CommonJS/ESM、HTTP 版本、跨域安全、缓存、URL 访问链路、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,而不是只看版本号。
3. 跨域请求如何定位?
同源由协议、主机和端口共同决定。CORS 不是“让服务器允许一切”,而是服务器通过响应头授予浏览器脚本读取跨源响应的权限。
- 简单请求可以直接发送,浏览器仍检查
Access-Control-Allow-Origin。 - 非简单请求通常先发
OPTIONS预检,服务端需返回允许的方法和请求头。 - 携带 Cookie 或认证信息时,必须显式指定 origin,不能同时使用
Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true。 - 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。
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 和前端过滤只能改善体验,不能替代服务端校验。
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 后可被读取。最终授权永远在服务端完成。
深入阅读:HTTP 缓存与头部、JWT 鉴权、浏览器存储与安全。
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 允许一个构建在运行时消费另一个构建暴露的模块,适合独立发布的微前端或跨团队共享功能。它并不自动解决版本、权限、网络和运行时隔离:
- 远程入口不可用时要有超时、降级 UI 或旧版本回退。
- React/Vue 等 shared 依赖要明确 singleton、版本范围和初始化顺序,避免多个运行时导致状态/Hook 错误。
- 远程代码仍是供应链输入,应做签名/来源校验、CSP、审计和灰度;不能把任意 URL 当可信模块。
- 远程模块的 CSS、路由、事件和全局副作用要有命名空间与卸载清理。
面试中把它和 iframe、qiankun、npm 包作对比:Federation 偏运行时模块共享,iframe 隔离更强但通信成本高,npm 偏构建时复用;选型取决于独立部署、隔离、性能和团队边界。
10. 口述练习
任选一题,用“结论 -> 机制 -> 约束 -> 失败 -> 验证”回答:
- HTTP/2 仍然卡顿,你先看哪些证据?
- 本地代理成功但线上 CORS 失败,差异可能在哪里?
- 修改一个组件后所有 chunk hash 都变了,如何定位?
- 远程微前端加载失败时,如何保证主应用可用?
来源:高频真题解析与9月考点预测上.pdf、高频真题解析与9月考点预测下.pdf。宣传页和外部文章链接未作为结论;协议版本和构建行为按当前浏览器/工具边界补充。