DrillLab
八股题库Question bank

105 道问答题,一道一卡105 questions, one card each

默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。Only the question shows by default. Answer it in your head first, then open the answer. Mark the ones you miss; the flashcard round puts those first.

0 / 105道自评过self-assessed
0Got it0模糊Shaky0不会No idea105还没做Not seen
正在读这台浏览器里的标记…Reading your marks from this browser…
标记不是打分,是给下一轮排队Marks are a queue, not a score

标「不会」的题会排到抽认卡最前面,标「会」的进低频池排最后。所以别客气 —— 觉得答得磕磕巴巴就标「模糊」。准备好了就去抽认卡“No idea” jumps to the front of the next round; “got it” drops to the back. So be honest — if it came out shaky, mark it shaky. Then go run a flashcard round.

题目Questions

筛出 4 道(共 105 道)。4 of 105 questions.
Node / ExpressNode / Express#313

Node.js 的事件循环是怎么工作的

How does the event loop work in Node.js

看答案Show answer

一句话:Node 是单线程执行 JS + 多线程做 I/O。 主线程跑 JS, 耗时的 I/O 交给 libuv 的线程池或操作系统, 完成后把回调放进队列, 事件循环再取出来执行。

六个阶段(顺序要记住):

  1. timers—— 到期的 setTimeout /setInterval
  2. pending callbacks—— 上一轮延后的系统回调
  3. idle / prepare —— 内部用
  4. poll——取新的 I/O 事件, 大部分时间待在这里
  5. check——setImmediate 的回调
  6. close callbacks——socket.on("close") 这类

关键:每个阶段之间都会把微任务清空。而 Node 的微任务分两级:process.nextTick的优先级比 Promise 更高—— 它有自己的队列,在所有 Promise 微任务之前执行

和浏览器的差别(这是考点):浏览器只有「宏任务 / 微任务」两档, 每次只取一个宏任务; Node 分了六个阶段, 同一阶段里的队列会一次取完
另外 Node 多了setImmediateprocess.nextTick这两个浏览器没有的东西。

会追问:setTimeout(fn, 0)setImmediate 谁先?」——不确定! 在主模块里两者顺序取决于进程启动耗时; 但在 I/O 回调里,setImmediate 一定更早(因为 check 阶段紧跟在 poll 后面, 而 timers 要等下一轮)。能答出「主模块里不确定」很加分。
「CPU 密集任务怎么办?」—— 单线程会被卡死。 用 worker_threads、 子进程,或者干脆交给别的服务。

In one line: Node runs your JavaScript on one thread and its I/O on many. The main thread runs JS; anything slow goes to libuv’s thread pool or straight to the OS, and when it finishes the callback is queued for the event loop to pick up.

Six phases, and the order matters:

  1. timerssetTimeout and setInterval callbacks that are due
  2. pending callbacks — system callbacks deferred from the previous turn
  3. idle / prepare — internal use
  4. pollpicks up new I/O events; this is where the process spends most of its time
  5. checksetImmediate callbacks
  6. close callbacks — things like socket.on("close")

The key point: microtasks are drained between every phase. And Node has two levels of them — process.nextTick outranks Promises. It gets its own queue and runs before any Promise microtask.

How this differs from the browser (this is the part they are testing): the browser has just two tiers, macrotask and microtask, and takes one macrotask per turn. Node has six phases, and within a phase it drains the whole queue.
Node also has setImmediate and process.nextTick, neither of which exists in a browser.

Follow-up: “Which fires first, setTimeout(fn, 0) or setImmediate?” — it is not guaranteed. At the top level of the main module the order depends on how long startup took. But inside an I/O callback setImmediate always wins, because check comes right after poll while timers has to wait for the next turn. Saying “undefined in the main module” is the answer that earns you points.
“What about CPU-bound work?” — one thread means it blocks everything. Use worker_threads, a child process, or hand the job to a different service entirely.

JavaScriptNode 的执行顺序示意Illustrative
1console.log("1 同步");
2
3setTimeout(() => console.log("2 timers"), 0);
4setImmediate(() => console.log("3 check"));
5Promise.resolve().then(() => console.log("4 promise 微任务"));
6process.nextTick(() => console.log("5 nextTick"));
7
8console.log("6 同步");
9
10// 输出:1 同步 -> 6 同步 -> 5 nextTick -> 4 promise 微任务
11// -> 2 timers 和 3 check(这两个的相对顺序在主模块里不保证)
12//
13// 记住:nextTick 比 Promise 更优先,这是 Node 独有的
1console.log("1 sync");
2
3setTimeout(() => console.log("2 timers"), 0);
4setImmediate(() => console.log("3 check"));
5Promise.resolve().then(() => console.log("4 promise microtask"));
6process.nextTick(() => console.log("5 nextTick"));
7
8console.log("6 sync");
9
10// Output: 1 sync -> 6 sync -> 5 nextTick -> 4 promise microtask
11// -> 2 timers and 3 check (their relative order is not guaranteed in the main module)
12//
13// Remember: nextTick runs before Promise, and that part is specific to Node
Node / ExpressNode / Express#314

请求 - 响应周期是怎样的

Explain the request & response cycle

看答案Show answer

一句话:客户端发请求 → 经过一串中间件 → 匹配到路由 → 处理逻辑(查库等)→发出一个响应→ 结束。

Express 里具体经过什么:

  1. 解析——express.json()把请求体解析成 req.body不加这个中间件req.body 就是undefined, 这是最常见的新手问题)
  2. 通用中间件—— CORS、日志、关联 id、限流
  3. 认证 / 鉴权—— 验 token,把用户挂到req.user
  4. 路由匹配—— 按注册顺序找第一个匹配的 method + path
  5. 校验 → 业务逻辑 → 查库
  6. 响应——res.status(200).json(...)
  7. 兜底—— 404 处理 + 错误处理中间件

三个必须知道的规则:

  • 中间件按注册顺序执行, 必须调 next()才会往下走 —— 忘了调请求就永远挂着, 客户端一直转圈。
  • 一个请求只能响应一次。重复 res.send会报Cannot set headers after they are sent所以调用响应之后一定要return
  • 错误处理中间件有四个参数(err, req, res, next)——少一个参数 Express 就认不出它是错误处理器, 这个坑很经典。

会追问:「异步函数里抛的错误 Express 会自动捕获吗?」——Express 4 里不会, 会变成未处理的 rejection,请求挂死。 要么自己 try/catchnext(err), 要么包一层 asyncHandler。Express 5 才支持自动转发。

In one line: the client sends a request, it walks through a chain of middleware, a route matches, the handler does its work (hits the database and so on), one response goes out, done.

What it actually passes through in Express:

  1. Parsingexpress.json() turns the body into req.body (without that middleware req.body is undefined, which is the single most common beginner bug)
  2. Generic middleware — CORS, logging, a correlation id, rate limiting
  3. Authentication and authorisation — verify the token, hang the user off req.user
  4. Route matching — the first registered method + path that matches wins
  5. Validation, then business logic, then the database
  6. Response res.status(200).json(...)
  7. Catch-alls — the 404 handler and the error-handling middleware

Three rules you have to know:

  • Middleware runs in registration order and you must call next() for the chain to continue. Forget it and the request hangs forever while the client spins.
  • One request gets exactly one response. A second res.send throws Cannot set headers after they are sent. So always return after you respond.
  • Error middleware takes four arguments, (err, req, res, next) drop one and Express no longer recognises it as an error handler. A classic trap.

Follow-up: “Does Express catch errors thrown inside an async function?” — not in Express 4. It becomes an unhandled rejection and the request hangs. Either try/catch yourself and call next(err), or wrap the handler in an asyncHandler. Express 5 forwards them for you.

JavaScript一个请求经过的全部环节示意Illustrative
1app.use(express.json()); // 1 解析 body
2app.use(cors()); // 2 通用
3app.use(correlationId); // 关联 id,方便串联日志
4app.use(auth); // 3 认证
5
6app.get("/orders/:id", async (req, res, next) => {
7 try {
8 const order = await db.find(req.params.id);
9 if (!order) return res.status(404).json({ error: "not found" }); // 记得 return
10 res.json(order);
11 } catch (e) {
12 next(e); // Express 4 不会自动接异步错误
13 }
14});
15
16app.use((req, res) => res.status(404).json({ error: "no route" }));
17
18// 错误处理中间件:必须是四个参数,少一个就不生效
19app.use((err, req, res, next) => {
20 console.error(err);
21 res.status(500).json({ error: "internal" });
22});
1app.use(express.json()); // 1 parse the body
2app.use(cors()); // 2 general
3app.use(correlationId); // a correlation id, so logs can be tied together
4app.use(auth); // 3 authentication
5
6app.get("/orders/:id", async (req, res, next) => {
7 try {
8 const order = await db.find(req.params.id);
9 if (!order) return res.status(404).json({ error: "not found" }); // do not forget return
10 res.json(order);
11 } catch (e) {
12 next(e); // Express 4 does not catch async errors for you
13 }
14});
15
16app.use((req, res) => res.status(404).json({ error: "no route" }));
17
18// The error-handling middleware must take four arguments; with three it never runs
19app.use((err, req, res, next) => {
20 console.error(err);
21 res.status(500).json({ error: "internal" });
22});
Node / ExpressNode / Express#315

查询参数 vs 路径参数

Query parameters vs Path parameters

看答案Show answer

一句话:路径参数标识「哪一个资源」/users/42),查询参数描述「怎么取」?page=2&sort=name)。

路径参数查询参数
形式/users/:id/users?role=admin
Express 里读req.params.idreq.query.role
Spring 里读@PathVariable@RequestParam
必填性必填(是路径的一部分)通常可选,有默认值
语义标识资源筛选、排序、分页、字段裁剪

设计判据一句话:「去掉它之后,还是同一个资源吗?」去掉 42 就不知道是谁了 → 路径参数; 去掉 ?page=2还是同一批用户,只是换一页 → 查询参数。

会追问:「密码能放查询参数吗?」——绝对不行。 URL 会进浏览器历史、 服务器访问日志、 Referer 头、CDN 日志 ——即使用了 HTTPS, URL 本身也会被大量记录。 敏感数据放请求体或请求头。
「查询参数有长度限制吗?」—— 规范没有,但实践上服务器和 CDN 通常限制 URL 在 2 KB 到 8 KB。 复杂查询条件多的搜索接口 有时会改用 POST 带 body。

In one line: a path parameter says which resource (/users/42); a query parameter says how you want it (?page=2&sort=name).

Path parameterQuery parameter
Shape/users/:id/users?role=admin
Read in Expressreq.params.idreq.query.role
Read in Spring@PathVariable@RequestParam
Required?Yes — it is part of the pathUsually optional, often with a default
MeaningIdentifies the resourceFiltering, sorting, paging, field selection

One test to decide: “Take it away — is it still the same resource?” Drop the 42 and you no longer know who you mean → path parameter. Drop ?page=2 and it is still the same set of users, just a different page → query parameter.

Follow-up: “Can a password go in a query parameter?” — absolutely not. URLs land in browser history, server access logs, the Referer header and CDN logs. HTTPS encrypts the connection; it does not stop the URL from being written down all over the place. Sensitive values go in the body or a header.
“Is there a length limit on query strings?” — not in the spec, but in practice servers and CDNs cap the URL somewhere between 2 KB and 8 KB. Search endpoints with complex filters sometimes switch to POST with a body for that reason.

Node / ExpressNode / Express#316

什么是 CRUD

What is CRUD

看答案Show answer

一句话:Create / Read / Update / Delete —— 数据操作的四种基本类型。 REST 把它们映射到 HTTP 方法。

操作方法路径成功状态码
CreatePOST/orders201 Created
Read(列表)GET/orders200
Read(单个)GET/orders/:id200,没有则 404
Update(整体)PUT/orders/:id200
Update(部分)PATCH/orders/:id200
DeleteDELETE/orders/:id204 No Content

两个高频追问:

① PUT vs PATCH。PUT整体替换—— 没传的字段应该被清空;PATCH局部更新—— 只改传了的字段。实践中很多人把 PUT 当 PATCH 用, 这是错的,但要知道现实如此。

② 幂等性。同一个请求发多次,结果一样就叫幂等。

  • GET / PUT /DELETE ——幂等
  • POST ——不幂等(发两次会创建两条)
  • PATCH ——看实现{ n: 5 } 幂等,{ n: { $inc: 1 } } 不幂等)

为什么重要:客户端重试、 网关超时重发时, 幂等的接口是安全的,POST 需要幂等键(idempotency key)来防重复下单。

再一个追问:「删一个不存在的资源返回什么?」——可以是 204 也可以是 404。 返 204 更符合幂等语义 (「删完了」这个结果达成了); 返 404 信息更明确。关键是团队内一致, 并且写进接口文档。

In one line: Create / Read / Update / Delete — the four basic things you do to data. REST maps them onto HTTP methods.

OperationMethodPathStatus on success
CreatePOST/orders201 Created
Read (list)GET/orders200
Read (one)GET/orders/:id200, or 404 if it is not there
Update (whole)PUT/orders/:id200
Update (partial)PATCH/orders/:id200
DeleteDELETE/orders/:id204 No Content

Two follow-ups they almost always ask:

① PUT vs PATCH. PUT is a full replacement — fields you leave out should be cleared. PATCH is a partial update — only the fields you send change. Plenty of real code uses PUT as if it were PATCH, which is wrong, but know that it happens.

② Idempotency. Send the same request more than once, get the same result — that is idempotent.

  • GET / PUT / DELETE idempotent
  • POSTnot idempotent (send it twice and you get two records)
  • PATCHdepends on the payload ({ n: 5 } is idempotent, { n: { $inc: 1 } } is not)

Why it matters: when a client retries or a gateway resends after a timeout, an idempotent endpoint is safe. POST needs an idempotency key to stop the same order being placed twice.

One more follow-up: “What do you return when deleting something that does not exist?” — 204 or 404, both defensible. 204 fits the idempotent reading (the outcome you asked for is true); 404 tells the caller more. What matters is that the team agrees and it is in the API docs.

这些题从哪来Where these come from

99 道来自面试题库 #269–#387;TypeScript 深度那 6 道是 DrillLab 自出的(senior 补强,卡片上有标注)。答案都是 DrillLab 写的,所以讲解里的代码块一律标「示意」。每道题都能点回它出处的那一节课。99 questions come from the interview bank (#269–#387); the 6 TypeScript deep-dive ones are DrillLab-made (marked on the card). All answers are written by DrillLab, so every code block here is labelled “demo”. Each card links back to the lesson it came from.
八股题库 / Interview drills · DrillLab