面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月27日 10:38

Swift 错误处理怎么做?throws、try、catch 详解

Swift 的错误处理核心就四个关键字:Error 定义错误、throws 声明可能失败、throw 抛出错误、try+do-catch 捕获处理。整个流程编译器强制检查——throws 函数必须被 try 调用,try 必须在 do-catch 或另一个 throws 函数中,不可能"忘了处理错误"。这点和 Java 的 checked exception 思路类似,但 Swift 更轻量。try 三种形式各有用途:try(标准,需 do-catch)、try?(失败返回 nil,丢弃错误详情)、try!(断言不会失败,否则 crash)。日常 80% 场景用 try? 就够了——只关心成功还是失败,不需要区分原因。需要根据错误类型走不同分支时才用完整 do-catch。try! 只在逻辑上不可能失败的路径或单元测试中用。错误沿调用链自动向上传播:A throws → B 调用 A 也必须 throws → C 调用 B 也必须处理,直到某个调用方用 do-catch 消化。传播链编译器强制检查,不存在运行时才发现错误没处理的情况。defer 无论是否抛错都执行,用于资源清理,多个 defer 按栈序(后进先出)执行。rethrows 专用于高阶函数——函数本身不抛错,但传入闭包如果是 throws 的,调用方也必须 try。标准库 map、filter、forEach 都用了 rethrows:传普通闭包不强制 try,传 throws 闭包才需要。Swift 5 的 Result 把错误从控制流变成值,可以像处理枚举一样处理成功和失败,特别适合异步回调。async/await 普及后新代码用 async throws 更直观,Result 主要兼容旧接口。Swift 错误处理不是传统"异常"——不会 unwinding 调用栈,happy path 几乎零开销。追问try? 和 try! 有什么区别?什么场景用哪个?try? 失败返回 nil;try! 失败直接 crash。绝大多数场景用 try?,比如读配置文件读不到就用默认值。try! 只在逻辑上不可能失败时用,比如解析 Bundle 内的 JSON——这都能失败说明资源缺失,是开发阶段就该暴露的 bug,不应静默处理。rethrows 和 throws 有什么区别?throws 表示函数自身会抛错;rethrows 表示函数本身不抛错,但透传闭包参数的错误。关键区别:rethrows 函数传入不抛错的闭包时,调用方不需要 try。Array.map 用 rethrows,所以 arr.map { $0 + 1 } 不需要 try,arr.map { try throwingFunc($0) } 才需要。Result 和 do-catch 怎么选?同步用 do-catch;异步回调用 Result,因为 completion handler 里抛出的错误外层 catch 不到。async/await 之后新代码直接 async throws 更清晰。Result(catching: { try func() }) 可以把 throws 函数包装成 Result,方便在回调中传递。defer 有什么常见坑?多个 defer 后进先出。坑一:在循环里写 defer 以为每次迭代都执行,实际等函数作用域结束。坑二:defer 不等异步操作完成,需要异步清理得用其他机制。正确做法:打开资源后立刻写 defer 关闭。Swift 错误处理和 Java 异常有什么本质区别?Swift 的错误不会 unwinding 调用栈,happy path 零开销。Java checked 异常强制 catch 或声明,代码侵入性强;Swift 更轻量,try? 提供了快速降级路径。Swift 没有 finally,用 defer 替代,且 defer 不限于 do-catch 块,任何作用域都能用。写段代码enum APIError: Error { case invalidURL case badStatus(Int)}func fetchUser(id: String) throws -> User { guard let url = URL(string: "https://api.example.com/users/\(id)") else { throw APIError.invalidURL } let (data, resp) = try URLSession.shared.data(from: url) guard let http = resp as? HTTPURLResponse, http.statusCode == 200 else { throw APIError.badStatus((resp as? HTTPURLResponse)?.statusCode ?? -1) } return try JSONDecoder().decode(User.self, from: data)}do { let user = try fetchUser(id: "42")} catch APIError.badStatus(let code) { print("HTTP \(code)")} catch { print(error)}let maybe = try? fetchUser(id: "42") // User?
前端阅读 185月27日 01:17

JavaScript 异步解决方案的发展历程及优缺点

JS 异步方案演进:Callback(回调):异步操作完成时调用。缺点:回调地狱(嵌套超过 3 层就难读)、错误处理分散、流程控制难(并行/串行需要自己写计数器)Promise(ES6):链式调用可读性提升,.catch() 统一错误处理,Promise.all/race 等内置并行控制。缺点:长链仍不好读,一旦进入 .then() 链中间没法跳出(无法取消)Generator + co(ES6):yield 暂停执行,配合自动执行器实现"看起来同步"的代码。缺点:需要额外库(co),需要理解 Generator 概念,心智负担高async/await(ES8):Promise + Generator 的语法精华。写法像同步,错误处理用 try-catch,分支和循环直接写。缺点:滥用串行 await 破坏并发性能(两个无关请求应放 Promise.all)追问什么时候不推荐用 async/await?简单场景(单次 .then().catch() 比 async 包装更简洁)需要并发时(多个 await 写在一起是串行的)顶层模块代码中(ES6 模块顶层已有 await,但 CJS 不支持)数组方法中(.map(async fn) 返回 Promise 数组,需要再 Promise.all)Promise 可以取消吗?原生 Promise 不支持取消。但有 AbortController 变通方案:fetch 传入 signal,abort 时 fetch 抛 AbortError,.then() 不会执行。真正意义的 Promise 取消需要第三方库(如 bluebird)或用 RxJS 的 Subscription。
前端阅读 485月27日 01:17

== 和 === 的区别是什么?什么情况下用 == 相等?

=== 是严格相等:类型不同直接 false,类型相同才比较值。== 是宽松相等:类型不同时做类型转换(强制类型转换),然后再比较。大多数场景用 ===。但 == 也有实际用途:if (x == null) —— 等价于 x === null || x === undefined,很简洁你明确知道两端类型相同时(和 === 没区别)处理字符串和数字比较时('5' == 5 是 true),比如从 input 里读出来的值// == 的经典坑'' == 0; // true[] == 0; // true[] == ''; // true[] == ![]; // true (?!)null == undefined; // trueNaN == NaN; // false (即使 === 也是 false)追问Object.is 和 === 有什么区别?两个不同:Object.is(NaN, NaN) 是 true(=== 是 false),Object.is(0, -0) 是 false(=== 是 true)。其他行为和 === 完全一致。if (x == null) 比 if (x === null || x === undefined) 有什么风险吗?几乎没有。== null 只在值为 null 或 undefined 时为 true,对 0、''、false 都是 false。这是 == 唯一一个业界认可的"干净"用法。
前端阅读 255月27日 01:17

JavaScript 的暂时性死区是什么?

暂时性死区(TDZ)是 let 和 const 的特性:从块级作用域开始到变量被声明为止,这个区间内访问变量会抛 ReferenceError。console.log(x); // undefined — var 提升但不报错var x = 1;console.log(y); // ReferenceError — let 有 TDZlet y = 2;let/const 确实有变量提升(引擎知道这个变量存在于作用域中),但在声明语句之前,这个绑定处于"未初始化"状态——这就是 TDZ。var 提升后直接被初始化为 undefined,所以能用。TDZ 的意义:帮你发现"在声明前就使用变量"的错误——这种 bug 用 var 时会被悄悄忽略。追问typeof 在 TDZ 中也会报错吗?会。typeof x 在 x 的 TDZ 中直接 ReferenceError。这是 typeof 唯一不安全的场景——通常 typeof 未声明变量 返回 "undefined" 不会报错,但 TDZ 是"已声明但未初始化",typeof 也会报错。TDZ 对函数参数的默认值有影响吗?有。参数默认值中引用的后面的参数,会遇到 TDZ:function fn(a = b, b = 1) {} // a 的默认值引用 b 时,b 还在 TDZ 中fn(); // ReferenceError
前端阅读 495月27日 01:17

Promise 和 async/await 和 Callback 有什么区别?

三个阶段的异步方案,层层递进:Callback:把后续操作作为回调函数传给异步操作。问题是回调地狱——多层嵌套横向增长,错误处理每个回调都得单独处理。Promise:把回调包装成对象,链式 .then() 解决横向嵌套,.catch() 统一处理错误。但长链仍不够直观,且 .then() 里不能直接用 try-catch。async/await:Promise 的语法糖。async 函数返回 Promise,await 暂停执行等结果。写法就是同步代码的样子,错误用 try-catch。本质还是 Promise——await 的值就是 .then() 回调的参数。// 三个方案的同一操作// CallbackgetData((err, data) => { if (err) return; process(data); });// PromisegetData().then(process).catch(handleError);// async/awaittry { const data = await getData(); process(data); } catch { handleError(); }追问async/await 怎么处理并发请求?Promise.all([fetch1, fetch2]) 配合 await。不要写成 await fetch1(); await fetch2()——这样是串行的,第二个请求等第一个完成才发。async 函数返回的 Promise 和普通 Promise 有区别吗?没有本质区别。async 函数内部抛错等于 reject,return 值等于 resolve。唯一注意的是:async 函数返回的 Promise 是原生 Promise,即使你 return 的是一个 thenable 对象,也会自动包裹成 Promise。
前端阅读 325月27日 01:16

module.exports 和 exports 的区别是什么?export 和 export default 的区别是什么?

两对概念,一个在 CommonJS,一个在 ESModule。module.exports vs exports(CommonJS):module.exports 是真正的导出对象。exports 只是 module.exports 的引用(const exports = module.exports)给 exports 赋新值会断开引用,导出失败;module.exports 赋新值可以安全做法:只添加属性用 exports.foo = bar,需要替换整个导出用 module.exports = foo// 正确module.exports = { a: 1 };exports.b = 2;// 错误 — exports 被重新赋值,断开引用exports = { a: 1 }; // module.exports 还是 {}export vs export default(ESModule):export 是命名导出,可以有多个。导入时用 { name } 且名字必须匹配export default 是默认导出,每个模块只有一个。导入时可以取任意名字一个模块可以同时有命名导出和默认导出// 导入区别import { foo } from './a'; // 命名导出import foo from './a'; // 默认导出import foo, { bar } from './a'; // 两者都有追问为什么 export default 导入可以随意命名?因为默认导出本质上导出的是 { default: value } 这个特殊 key。import x from 就是取 default key 的值。因此也叫 default import。项目中应该优先用命名导出还是默认导出?命名导出更好——IDE 自动补全、refactor 改名时更安全、Tree-Shaking 友好。默认导出适合"这个模块只有一个主要导出"(如一个组件、一个工具函数)。但争议是社区级的,没有绝对的优劣。
前端阅读 125月27日 01:16

一个 DOM 必须要操作几百次,该如何优化?

核心思路:批量操作,减少重排次数。DocumentFragment:创建一个脱离文档流的容器,在内存中构建完所有 DOM 再一次性插入。Fragment 插入后自己会消失,只留下子节点。const fragment = document.createDocumentFragment();for (let i = 0; i < 500; i++) { const li = document.createElement('li'); li.textContent = i; fragment.appendChild(li);}ul.appendChild(fragment); // 一次 DOM 操作display: none:先把容器隐藏,批量改完再显示。隐藏期间的 DOM 操作不触发重排(元素不参与布局计算)。cloneNode:克隆节点,在克隆上做修改,改完后替换原节点。虚拟列表:不是优化 DOM 操作次数,而是减少 DOM 节点总数——几百次操作通常意味着在渲染大量数据。只渲染视口内可见的几十个元素,滚动时复用。追问DocumentFragment 和直接 appendChild 性能差多少?大量操作时差几个数量级。直接 appendChild 每次操作都触发一次重排(元素加入布局树)。Fragment 里的操作不触发重排,只最后 append 时触发一次。为什么不用 innerHTML 一次性拼接字符串?innerHTML 确实比逐个创建 DOM 快,但有 XSS 风险(用户输入会执行脚本)。纯服务端数据可以用 innerHTML,有用户数据用 createElement + textContent。
前端阅读 965月27日 01:16

Koa.js 如何实现文件上传的断点续传?

断点续传的本质是"客户端记住传到哪了,服务端知道从哪继续接"。核心流程:上传前计算文件 hash(MD5/SHA1),作为文件唯一标识发请求到服务端查"这个文件的哪些分片你有了"(返回已上传分片索引)客户端只上传缺失的分片服务端暂存每个分片全部分片上完后,服务端合并分片为完整文件Koa 侧关键点:用 @koa/multer 或直接读 stream 接收分片分片命名规则:{hash}-{index},便于按 hash 查找和按 index 排序合并分片前校验每个分片的大小是否正确合并完后校验完整文件的 hash 是否和客户端一致追问分片大小怎么定?一般 1-5MB。太小请求次数多(HTTP 开销),太大断点续传意义不大了。网速好的用户可以用更大的分片。并发上传多个分片好还是串行好?并发上传更快,浏览器对同一域名的 HTTP/1.1 最大并发是 6 个(HTTP/2 不受限)。注意并发数不能太大——文件 I/O 是性能瓶颈,服务端同时写入大量分片会 IO 打满。合并完大文件后内存会炸吗?不会,用 fs.createWriteStream(流式写入)和 fs.createReadStream(流式读取)顺序追加。Koa 生态有 fs-extra 库做这些操作,底层是流式的不会一次性加载整个文件到内存。
前端阅读 305月27日 01:16

var、let、const 之间的区别是什么?

三个维度的区别:作用域:var 是函数作用域,let/const 是块级作用域。{ } 内部用 let 声明的变量,括号外访问不到。变量提升:var 有提升且初始化为 undefined(声明前访问得到 undefined)。let/const 也有提升但存在暂时性死区(TDZ)——声明前访问直接 ReferenceError。重复声明:var 可重复声明(后覆盖前),let/const 在同一作用域不能重复声明。const 额外特性:声明时必须初始化,且不能重新赋值。但对象和数组的属性可以修改(const 锁的是绑定,不是值)。// var:函数作用域,讨厌的经典 bugfor (var i = 0; i < 3; i++) { setTimeout(() => console.log(i)); // 3 3 3}// let:块级作用域,每次迭代创建新绑定for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i)); // 0 1 2}追问为什么 let 能解决 for 循环的回调/闭包问题?var 是整个 for 循环共享一个变量,循环结束后 i 是最终值。let 每次循环迭代都会创建一个新的绑定,每个 setTimeout 捕获的 i 是不同的绑定。即使循环结束后,这些绑定的值仍然保留着当时的 i。const 声明的对象属性为什么可以修改?const 锁定的是变量名到值的绑定关系——"这个变量名不能指向别的值"。对象属性是变量指向的内存地址内部的变更,不改变绑定关系。
前端阅读 435月27日 01:16

WeakSet、WeakMap 和 Set、Map 之间的区别是什么?

核心区别就一条:Weak 版本的 key(或元素)是弱引用,不阻止垃圾回收。Set vs WeakSet:Set 元素可以是任何类型;WeakSet 元素只能是对象Set 可迭代(forEach、size、keys);WeakSet 不可迭代Set 中对象被引用着,即使对象其他地方不再使用也不会被 GC;WeakSet 中对象没有其他引用时会被回收Map vs WeakMap:Map 的 key 可以是任何类型;WeakMap 的 key 只能是对象Map 可迭代;WeakMap 不可迭代WeakMap 条目会随 key 对象被 GC 而自动清除WeakMap 典型场景:Vue 3 的响应式依赖追踪、存储 DOM 节点的关联数据、为第三方对象附加元数据而不造成内存泄漏。WeakSet 用得少——需要标记"这个对象我见过"但不想阻止它被 GC 时用。追问为什么 WeakMap 没有 size 属性?因为 WeakMap 中条目可能随时被 GC 回收,size 值是瞬时的、不可靠的。如果 JS 引擎提供了 size,开发者的代码里依赖了这个值,但下一秒 GC 跑了一次值变了——这种不可预测性比没有 size 更糟糕。WeakMap 和 Map 在内存管理上有什么区别?Map 的 key 被引用着,即使这个 key 对象在别处都已不使用,Map 里的引用也会阻止 GC——内存泄漏风险。WeakMap 的 key 是弱引用,如果 key 对象没有其他强引用了,GC 可以回收,对应的 WeakMap 条目自动消失。
前端阅读 315月27日 01:15

ES6 中有哪些解决异步的方法?

ES6 之后异步方案演进:回调函数:最原始的方式,问题是回调地狱(callback hell)——多层嵌套,错误处理困难。Promise:ES6 引入。把回调的嵌套转成 .then() 的链式调用,用 .catch() 统一处理错误。解决了回调地狱,但长链 .then() 仍然不够直观。Generator + co:通过 yield 暂停函数执行,配合自动执行器(如 co 库)实现类似同步的写法。现在基本被 async/await 取代。async/await:ES8(ES2017)正式引入。Promise 的语法糖——async 函数返回 Promise,await 暂停执行等待 Promise 完成。写法最像同步代码:async function getData() { const res = await fetch('/api'); const data = await res.json(); return data;}追问Promise.all、Promise.allSettled、Promise.race、Promise.any 的区别?all:全成功才成功,一个失败就失败allSettled:等全部完成(不管成败),返回结果数组含状态标记race:第一个完成的就返回(不管成败)any:第一个成功的就成功,全失败才失败(和 race 相反)async/await 的错误怎么处理?try-catch 包裹 await。或者用 .catch() 链在 async 函数的返回值上。也可以用 await promise.catch(() => fallbackValue) 的模式给错误设默认值。
前端阅读 245月27日 01:12

React 组件渲染过程是怎么样的?

React 的渲染分两个阶段:Render 阶段(协调 Reconciliation):状态变化 → 创建新的 Virtual DOM 树 → 和旧的 Virtual DOM 做 diff → 标记需要更新的节点。这个阶段是纯计算,没有副作用,React 18 可以中断和恢复(并发模式)。Commit 阶段:把 diff 结果应用到真实 DOM。这个阶段不可中断,React 保证 DOM 更新的原子性。commit 结束后触发 useLayoutEffect 同步执行,然后浏览器重绘,最后异步执行 useEffect。React 18 之前 render 不可中断。18 的并发渲染(Concurrent Features)可以在 render 阶段暂停低优先级更新,优先处理用户交互等高优先级任务。追问什么情况下 React 会跳过组件的渲染?shouldComponentUpdate 返回 false(class 组件)React.memo 包裹的组件 props 没变(浅比较)state 没变化时(setState 传入相同值,React 用 Object.is 判断)Context value 没变时(但 Provider 下的所有 Consumer 会在 Provider render 时重新渲染,和 value 是否变化无关——这是常见性能陷阱)useLayoutEffect 和 useEffect 执行的时机有什么不同?useLayoutEffect 在 DOM 变更后、浏览器绘制前同步执行(阻塞渲染)。useEffect 在浏览器绘制后异步执行。需要读取 DOM 布局、同步更新防止闪烁的场景用 useLayoutEffect。React 18 并发模式的 "可中断渲染" 是怎么实现的?基于时间切片(Time Slicing)——Render 阶段被切成 5ms 的小段,每段结束后检查是否有更高优先级任务。如果有就暂停当前渲染,先处理高优任务。实现依赖的是 MessageChannel(而非 requestIdleCallback,因为后者在后台标签页可能被暂停)。
前端阅读 125月27日 01:12

XML 和 JSON 的区别是什么?

JSON 现在是默认的数据交换格式。XML 在上一个时代扮演过同样角色。最大的区别:JSON 是数据格式,XML 是标记语言。JSON 直接表达对象、数组、字符串,XML 用标签包裹数据,更侧重文档结构。// JSON{ "name": "张三", "age": 30 }<!-- XML --><person> <name>张三</name> <age>30</age></person>JSON 的优势:更轻量、解析更快(JSON.parse vs DOM/SAX 解析)、天然映射 JS 对象、Schema 更简单。XML 仍有用的场景:需要属性+值+命名空间(SOAP 协议)、需要 DTD/Schema 验证、大量文档处理时 XPath/XSLT 更灵活。追问为什么 JSON 取代了 XML 做 Web API 的数据格式?两点:JS 生态的爆炸式增长(JSON 是 JS 原生支持的,XML 需要额外解析)和移动端的带宽敏感(JSON 更紧凑)。REST API + JSON 几乎成了标配。JSON 有什么缺点?不支持注释(JSONC 是变体)不支持日期类型(需转换成字符串或时间戳)浮点数精度问题(0.1 + 0.2 !== 0.3 在不同 JSON 解析器中处理不一致)没有二进制支持(Base64 编码后体积膨胀)XML 现在还用在哪?银行、政府系统(SOAP 协议)Office 文档格式(docx、xlsx 本质上是 XML 的 ZIP 包)SVG 图像Android 布局文件RSS/Atom 订阅
服务端阅读 985月27日 01:12

Node.js 如何开启多进程?进程之间如何通讯?

Node.js 用 child_process 模块创建子进程,用 cluster 模块做多核利用:child_process:spawn(command, args):启动一个新进程,返回流(适合长时间运行、大量输出的进程)exec(command, callback):启动 shell 执行命令,缓存输出后回调(适合短命令)fork(modulePath):特殊 spawn,创建 Node.js 子进程,自带 IPC 通道cluster:基于 fork 封装,能创建多个共享同一端口的 worker 进程(常见于 HTTP 服务利用多核)。进程通讯:fork 创建的父子进程间有 IPC 通道,用 process.send(msg) 和 process.on('message') 通信。底层实现:libuv 管道(pipe)。追问cluster 怎么实现多进程共享端口?主进程监听端口,将接收到的连接通过 Round-Robin 分发给 worker 进程。worker 不直接监听端口,而是接收主进程分配的连接句柄。Linux 上也可用 SO_REUSEPORT 内核级别的分发。fork 和 spawn 的区别?fork 是 spawn 的特殊版——专门 fork Node.js 进程,自动建立 IPC 通道。spawn 启动任何命令,流式处理输出,适合与外部程序交互。PM2 的 cluster 模式和 fork 模式有什么区别?cluster:PM2 用 Node.js cluster 模块,多实例共享端口,自动负载均衡fork:PM2 只是用 child_process.fork 启动多个实例,需要不同端口或用 Nginx 做反向代理
前端阅读 685月27日 01:12

TCP 建立连接需要经过哪几步?

TCP 三次握手建立连接:客户端 → SYN:客户端发 SYN=1,seq=x(随机初始序列号)。客户端进入 SYN-SENT 状态服务端 → SYN+ACK:服务端回 SYN=1,ACK=1,seq=y,ack=x+1。服务端进入 SYN-RCVD 状态客户端 → ACK:客户端发 ACK=1,seq=x+1,ack=y+1。双方进入 ESTABLISHED 状态为什么是三次不是两次?因为要防止已失效的连接请求到达服务端。如果只有两次,客户端发了一个 SYN 因为网络延迟没到,客户端超时重发了一个新的 SYN 建立了连接。之后旧的 SYN 到达服务端,服务端以为这是新连接,回 SYN+ACK 就建立了连接——但客户端根本不知道这个连接的存在。追问为什么不是四次握手?理论上四次(SYN → SYN+ACK → ACK → 服务端收到 ACK 确认)更稳妥。但第三步的 ACK 可以和服务端收到 ACK 合并——服务端只需要知道客户端收到了自己的 SYN+ACK 就够。额外的第四次是冗余的。SYN 泛洪攻击是什么?攻击者发送大量 SYN 但不回 ACK,导致服务端大量连接处于 SYN-RCVD 半连接状态,耗尽服务端资源。防御:SYN Cookie(服务端不分配资源,用 Cookie 验证客户端是真的)、减少 SYN-RCVD 超时时间。TCP 四次挥手为什么多一次?因为 TCP 是全双工的——每个方向都要独立关闭。客户端 FIN 表示"我说完了",服务端 ACK 表示"知道了",但如果服务端还有数据要发,发完后再 FIN。所以是 FIN → ACK → FIN → ACK 四步。
计算机基础阅读 925月27日 01:11

GET 和 POST 的区别是什么?何时使用 POST?

从 HTTP 协议本身看,GET 和 POST 只有两个本质区别:语义不同:GET 是"获取"(安全、幂等),POST 是"提交数据以处理"(不幂等)POST 有 body,GET 没有(GET 也可以有 body,但 RFC 不推荐,很多实现不支持)其他所谓的区别——"GET 参数在 URL 里,POST 在 body 里"、"GET 参数长度有限制"、"GET 被浏览器缓存"——要么是实现细节,要么是浏览器的行为,不是协议本身的约束。何时用 POST:创建/修改资源、提交表单、需要传大量数据、需要隐藏敏感参数(虽然 body 也不安全,HTTPS 才是真正保护)。追问GET 请求真的不能有 body 吗?RFC 7231 没有禁止,但建议不要给 GET 加 body。实际上 fetch 和 XMLHttpRequest 都支持带 body 的 GET,但浏览器 <form> 和 <a> 不支持,很多中间件/代理也不处理 GET body。PUT 和 POST 有什么区别?都是提交数据,但语义不同:PUT 是幂等的(多次调用结果相同——替换指定位置的资源),POST 不是(多次调用可能创建多个资源)。PUT 的 URL 通常确定具体资源(/users/1),POST 是集合 URL(/users)。POST 比 GET 更安全吗?不安全。POST body 在 HTTP 下仍然是明文传输,只是 URL 里看不到而已。HTTPS 下二者都加密后才发送,POST 的"安全性"优势在 HTTPS 下不存在。真正的区别是 POST 数据不会出现在日志/浏览器历史/server log 的 URL 中。
前端阅读 245月27日 01:10

webpack 的热更新原理是什么?详细聊聊热更新的流程

HMR(Hot Module Replacement)的核心流程:webpack-dev-server 启动 WebSocket 服务,和浏览器建立长连接文件变化 → webpack 重新编译 → 生成两个文件:[hash].hot-update.json(变更模块清单)和 [hash].hot-update.js(变更模块代码)通过 WebSocket 推送 hash 事件给浏览器浏览器用 AJAX 拉 hot-update.json,用 JSONP 拉 hot-update.jsHMR runtime 找到变更的模块,执行 module.hot.accept 注册的回调如果模块没有注册 accept,HMR runtime 向上冒泡,直到找到 accept 或刷新整个页面Vue-loader、style-loader 等 loader 内部自动调用了 module.hot.accept,所以改 .vue 文件能无刷新更新。追问为什么需要两步拉取(json + js)?hot-update.json 先确认哪些模块变了,如果当前页面没有引用变更模块,就不需要拉 hot-update.js。另外 json 由 webpack-dev-server 直接提供(内存文件系统),js 则走 JSONP 来避免跨域问题。React Fast Refresh 和普通 HMR 有什么区别?React Fast Refresh 在 HMR 基础上做了 React 组件的"签名"匹配——换掉的组件如果有相同的 hooks 调用和组件的导出签名,就保留下面的组件状态(state 不丢失)。普通 HMR 只是替换模块代码,状态会重置。HMR 失效了怎么排查?module.hot.accept 是否注册(检查对应 loader 是否配置正确)文件路径大小写不一致(macOS 不敏感但 webpack 敏感)配置文件中 hot: true 和 HotModuleReplacementPlugin 是否启用
计算机基础阅读 1145月27日 01:10

HTTP 有哪些常用的状态码?

按分类记:2xx 成功:200 OK:请求成功201 Created:创建资源成功(POST 返回)204 No Content:成功但无响应体(DELETE 后常见)3xx 重定向:301 Moved Permanently:永久重定向(浏览器会缓存,下次直接跳)302 Found:临时重定向304 Not Modified:资源未修改,用缓存(配合 If-Modified-Since/ETag)307/308:和 301/302 类似,但 POST 转 GET 行为不同4xx 客户端错误:400 Bad Request:请求格式错误401 Unauthorized:未认证403 Forbidden:已认证但无权限404 Not Found:资源不存在405 Method Not Allowed:方法不对(比如用 GET 调了 POST 接口)429 Too Many Requests:被限流5xx 服务端错误:500 Internal Server Error:服务端内部错误502 Bad Gateway:网关/代理收到无效上游响应503 Service Unavailable:服务暂不可用(维护/过载)504 Gateway Timeout:网关超时追问301 和 302 的区别?301 是永久的——浏览器会缓存重定向目标,之后相同 URL 直接跳转,不用再请求原地址。SEO 权重转移。302 是临时的——每次都会请求原地址再跳转。语义不同,对 SEO 和缓存影响很大。401 和 403 怎么区分?401 是"你是谁?"——没登录或 token 过期,需要你去认证。403 是"我知道你是谁,但不让你看"——认证通过了但权限不够。502 和 504 的区别?502 是网关从上游收到了无效/不完整的响应(服务崩溃、配置错误)。504 是网关在规定时间内没等到上游响应(超时)。排查方向:502 看服务有没有挂,504 看服务是不是太慢。
前端阅读 345月27日 01:10

Chrome 打开一个页面需要启动多少进程?分别有哪些进程?

Chrome 是多进程架构,主要进程:Browser 进程(1 个):主进程,管 UI(地址栏、书签)、网络请求、文件访问。协调其他进程。Renderer 进程(每个标签页一个):渲染页面内容(HTML、CSS、JS)。沙箱隔离,一个页面崩不影响其他页面。GPU 进程(1 个):处理 GPU 任务(CSS 3D 变换、WebGL、视频解码)。Plugin 进程(按需):隔离运行的插件(Flash 等,现已很少)。Network 进程(1 个,较新版本):独立的网络进程。Utility 进程(按需):音频、扩展等。同一站点(same-site)的标签页可能共享 Renderer 进程以节省内存。具体数量取决于你开了多少标签页、多少扩展。追问为什么 Chrome 用多进程而不是多线程?稳定性和安全性。如果一个标签页崩溃或卡死,只影响那个 Renderer 进程,不会拖垮整个浏览器。沙箱机制也只限制单进程的权限。缺点是内存占用高,所以 Chrome 引入了进程共享(同一站点共享 Renderer 进程)。Renderer 进程里是什么?Renderer 进程包含:主线程(JS 执行、样式计算、布局)、合成线程(处理滚动、动画)、Raster 线程(将绘制命令转成位图)、Worker 线程等。主线程阻塞是页面卡顿的主要原因。内存不够时 Chrome 会怎么做?Chrome 会动态管理:冻结后台标签页的进程(节省内存)、优先杀掉非活动标签页的 Renderer 进程、同一站点合用一个 Renderer 进程。开大量标签页时内存压力还是很大。
计算机基础阅读 435月27日 01:10

什么是对称加密和非对称加密?

对称加密:加密和解密用同一把密钥。快,适合加密大量数据。问题是怎么安全地把密钥传给对方——密钥传输过程中可能被截获。代表:AES、DES、ChaCha20。非对称加密:一对密钥——公钥加密、私钥解密。公钥可以公开,任何人用公钥加密,只有持有私钥的人能解密。解决了对称加密的密钥分发问题,但计算慢,不适合大数据。代表:RSA、ECC。实际应用中二者组合:用非对称加密传递对称密钥,之后的通信全部用对称加密。HTTPS 的 TLS 握手就是这样干的——RSA/ECDHE 交换密钥 → AES 加密通信内容。追问HTTPS 用的是对称还是非对称?都用。TLS 握手阶段用非对称(RSA/ECDHE)协商出一个对称密钥(Session Key),之后的数据传输用这个对称密钥做 AES 加密。取长补短。为什么非对称加密更安全但更慢?非对称加密的数学基础是大数分解(RSA)或椭圆曲线(ECC),运算量远大于 AES 的位运算。RSA 2048 位密钥加密几百字节就要几十毫秒,AES 加密几 MB 数据只要微秒级。前端的 crypto.subtle 能做非对称加密吗?能。crypto.subtle.generateKey 支持 RSA-OAEP 和 ECDH。但大部分场景 JD 不需要在前端做非对称加密——前端代码公开,私钥无处存放。通常前端只是用 HTTPS 加密传输,非对称部分在 TLS 层自动完成。