引言:全栈开发工程师的核心竞争力
全栈开发工程师(Full Stack Developer)是现代软件开发团队中的多面手,他们不仅需要掌握前端技术栈,还要精通后端开发、数据库设计以及系统部署等全方位技能。在技术面试中,面试官通常会从基础知识、项目经验、系统设计等多个维度来评估候选人的综合能力。本文将为你提供一份全面的面试题库,涵盖从基础到进阶的各个层面,帮助你在技术面试中游刃有余。
第一部分:前端技术面试题
1. HTML/CSS 基础
问题:请解释 HTML5 中的语义化标签,并举例说明其优势。
回答:
HTML5 引入了许多语义化标签,如 <header>、<nav>、<section>、<article>、<footer> 等,这些标签能够更清晰地描述网页内容的结构和意义。
优势:
- SEO 优化:搜索引擎更容易理解网页结构,提升搜索排名。
- 可访问性:屏幕阅读器等辅助工具能更好地解析页面内容。
- 代码可读性:开发者更容易理解页面结构,便于维护。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>语义化标签示例</title>
</head>
<body>
<header>
<h1>网站标题</h1>
<nav>
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">关于</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>文章标题</h2>
<p>文章内容...</p>
</article>
<section>
<h3>相关链接</h3>
<p>这里是相关链接区域...</p>
</section>
</main>
<footer>
<p>© 2023 我的网站</p>
</footer>
</body>
</html>
2. JavaScript 核心概念
问题:解释 JavaScript 中的闭包(Closure)及其应用场景。
回答: 闭包是指一个函数能够访问并记住其词法作用域,即使该函数在其词法作用域之外执行。闭包通常用于创建私有变量、实现数据封装和模块化。
应用场景:
- 数据封装:通过闭包可以创建私有变量,防止外部直接访问。
- 模块化:闭包是实现模块模式的基础。
- 回调函数:在异步编程中,闭包常用于保存上下文。
示例:
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count 变量无法从外部直接访问
// console.log(count); // 报错:count is not defined
3. 框架与库(React/Vue)
问题:React 中的虚拟 DOM 是什么?它如何提高性能?
回答: 虚拟 DOM(Virtual DOM)是 React 用于提高渲染性能的核心技术。它是一个轻量级的 JavaScript 对象,用于表示真实 DOM 的结构。当组件状态发生变化时,React 首先在虚拟 DOM 中进行更新,然后通过 Diff 算法比较新旧虚拟 DOM 的差异,最后只更新实际需要变化的 DOM 节点。
性能提升原因:
- 减少直接操作 DOM 的次数:DOM 操作是昂贵的,虚拟 DOM 通过批量更新减少重绘和回流。
- Diff 算法优化:React 的 Diff 算法能够高效地找出最小变化集,避免不必要的 DOM 操作。
示例:
// React 组件示例
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
// 每次点击按钮,React 会:
// 1. 更新状态 count
// 2. 重新渲染虚拟 DOM
// 3. 通过 Diff 算法比较新旧虚拟 DOM
// 4. 只更新实际变化的 DOM 节点(即显示计数的 <p> 标签内容)
第二部分:后端技术面试题
1. Node.js 基础
问题:解释 Node.js 中的事件循环(Event Loop)机制。
回答: Node.js 的事件循环是其非阻塞 I/O 模型的核心。事件循环允许多个 I/O 操作并发执行,而不需要为每个操作创建线程。事件循环分为多个阶段,每个阶段处理特定类型的任务。
事件循环的主要阶段:
- Timers:执行
setTimeout和setInterval的回调。 - I/O Callbacks:执行几乎所有的回调,除了 close、timers 和 setImmediate。
- Idle, Prepare:仅内部使用。
- Poll:检索新的 I/O 事件,执行 I/O 相关的回调。
- Check:执行
setImmediate的回调。 - Close Callbacks:执行 socket 的 close 事件回调。
示例:
console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 0);
setImmediate(() => {
console.log('Immediate callback');
});
Promise.resolve().then(() => {
console.log('Promise resolved');
});
process.nextTick(() => {
console.log('Next tick');
});
console.log('End');
// 输出顺序:
// Start
// End
// Next tick
// Promise resolved
// Timeout callback
// Immediate callback
2. RESTful API 设计
问题:设计一个用户管理系统的 RESTful API,包括用户注册、登录、获取用户信息、更新用户信息和删除用户。
回答: RESTful API 应遵循资源导向的设计原则,使用 HTTP 方法表示操作类型,使用状态码表示操作结果。
API 设计:
| 方法 | 路径 | 描述 | 状态码 |
|---|---|---|---|
| POST | /api/users |
用户注册 | 201 Created |
| POST | /api/users/login |
用户登录 | 200 OK |
| GET | /api/users/{id} |
获取用户信息 | 200 OK |
| PUT | /api/users/{id} |
更新用户信息 | 200 OK |
| DELETE | /api/users/{id} |
删除用户 | 204 No Content |
示例代码(Node.js + Express):
const express = require('express');
const app = express();
app.use(express.json());
// 模拟数据库
const users = [];
// 用户注册
app.post('/api/users', (req, res) => {
const { username, email, password } = req.body;
// 验证输入
if (!username || !email || !password) {
return res.status(400).json({ error: '缺少必要字段' });
}
// 检查用户是否已存在
if (users.find(u => u.email === email)) {
return res.status(409).json({ error: '用户已存在' });
}
// 创建用户(实际应用中应加密密码)
const newUser = {
id: Date.now().toString(),
username,
email,
password // 注意:实际应用中应使用 bcrypt 等库加密
};
users.push(newUser);
// 返回创建的用户信息(不包含密码)
const { password: _, ...userWithoutPassword } = newUser;
res.status(201).json(userWithoutPassword);
});
// 用户登录
app.post('/api/users/login', (req, res) => {
const { email, password } = req.body;
const user = users.find(u => u.email === email && u.password === password);
if (!user) {
return res.status(401).json({ error: '邮箱或密码错误' });
}
// 实际应用中应生成 JWT token
const { password: _, ...userWithoutPassword } = user;
res.json({
message: '登录成功',
user: userWithoutPassword,
token: 'example-jwt-token'
});
});
// 获取用户信息
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
const { password: _, ...userWithoutPassword } = user;
res.json(userWithoutPassword);
});
// 更新用户信息
app.put('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: '用户不存在' });
}
// 更新用户信息(实际应用中应验证权限)
users[index] = { ...users[index], ...req.body };
const { password: _, ...userWithoutPassword } = users[index];
res.json(userWithoutPassword);
});
// 删除用户
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: '用户不存在' });
}
users.splice(index, 1);
res.status(204).send();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
3. 数据库操作
问题:使用 SQL 和 MongoDB 分别实现用户注册功能,并比较两种数据库的优缺点。
回答:
SQL 实现(MySQL):
-- 创建用户表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户注册(使用预处理语句防止 SQL 注入)
-- 假设密码已使用 bcrypt 加密
INSERT INTO users (username, email, password_hash)
VALUES (?, ?, ?);
-- 查询用户是否存在
SELECT id FROM users WHERE email = ?;
MongoDB 实现:
// 用户注册
const mongoose = require('mongoose');
// 定义用户模式
const userSchema = new mongoose.Schema({
username: { type: String, required: true },
email: { type: String, required: true, unique: true },
password_hash: { type: String, required: true },
created_at: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
// 注册函数
async function registerUser(username, email, passwordHash) {
try {
// 检查用户是否已存在
const existingUser = await User.findOne({ email });
if (existingUser) {
throw new Error('用户已存在');
}
// 创建新用户
const newUser = new User({
username,
email,
password_hash: passwordHash
});
await newUser.save();
return newUser;
} catch (error) {
throw error;
}
}
SQL vs MongoDB 对比:
| 特性 | SQL (MySQL) | MongoDB |
|---|---|---|
| 数据结构 | 结构化,固定表结构 | 半结构化,灵活的文档结构 |
| 查询语言 | SQL | MongoDB 查询语言 |
| 事务支持 | 完整 ACID 事务支持 | 4.0+ 版本支持多文档事务 |
| 扩展性 | 垂直扩展为主 | 天然支持水平扩展 |
| 适用场景 | 需要复杂查询和事务的场景 | 需要灵活数据模型和快速迭代的场景 |
第三部分:系统设计面试题
1. 短链接系统设计
问题:设计一个短链接生成服务(类似 bit.ly)。
回答:
核心需求分析:
- 短链接生成:将长 URL 转换为短 URL(如
https://example.com/abc123) - 重定向:访问短链接时跳转到原始 URL
- 高可用性:支持高并发访问
- 数据统计:记录点击次数等信息
系统架构设计:
客户端 → API 服务层 → 数据存储层 → CDN/缓存层
数据库设计:
-- 短链接表
CREATE TABLE short_urls (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
short_code VARCHAR(10) UNIQUE NOT NULL,
original_url TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
click_count INT DEFAULT 0,
expires_at TIMESTAMP NULL
);
-- 点击日志表(用于统计分析)
CREATE TABLE click_logs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
short_code VARCHAR(10) NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
clicked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
短代码生成算法:
// 基于哈希的短代码生成
const crypto = require('crypto');
function generateShortCode(longUrl) {
// 1. 使用 MD5 生成哈希
const hash = crypto.createHash('md5').update(longUrl).digest('hex');
// 2. 取前 6 个字符作为短代码
let shortCode = hash.substring(0, 6);
// 3. 检查是否冲突(实际应用中需要循环处理)
// 4. 返回短代码
return shortCode;
}
// 基于自增 ID 的短代码生成(更推荐)
function generateShortCodeFromId(id) {
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const base = alphabet.length;
let shortCode = '';
while (id > 0) {
shortCode = alphabet[id % base] + shortCode;
id = Math.floor(id / base);
}
// 补齐到固定长度(可选)
return shortCode.padStart(6, '0');
}
API 实现(Node.js + Express):
const express = require('express');
const app = express();
app.use(express.json());
// 模拟数据库
const shortUrls = new Map();
const clickLogs = [];
// 生成短链接
app.post('/api/shorten', async (req, res) => {
const { longUrl } = req.body;
if (!longUrl) {
return res.status(400).json({ error: 'URL is required' });
}
// 验证 URL 格式
try {
new URL(longUrl);
} catch {
return res.status(400).json({ error: 'Invalid URL format' });
}
// 检查是否已存在
for (let [shortCode, url] of shortUrls.entries()) {
if (url === longUrl) {
return res.json({ shortUrl: `http://localhost:3000/${shortCode}` });
}
}
// 生成短代码(实际应用中需要处理冲突)
const shortCode = generateShortCodeFromId(Date.now());
// 存储映射关系
shortUrls.set(shortCode, longUrl);
res.json({
shortUrl: `http://localhost:3000/${shortCode}`,
shortCode
});
});
// 重定向
app.get('/:shortCode', (req, res) => {
const { shortCode } = req.params;
const longUrl = shortUrls.get(shortCode);
if (!longUrl) {
return res.status(404).json({ error: 'Short URL not found' });
}
// 记录点击日志(异步)
clickLogs.push({
shortCode,
ip: req.ip,
userAgent: req.get('User-Agent'),
timestamp: new Date()
});
// 更新点击计数(实际应用中应使用 Redis 缓存)
// 重定向
res.redirect(longUrl);
});
// 获取统计信息
app.get('/api/stats/:shortCode', (req, res) => {
const { shortCode } = req.params;
const longUrl = shortUrls.get(shortCode);
if (!longUrl) {
return res.status(404).json({ error: 'Short URL not found' });
}
const clicks = clickLogs.filter(log => log.shortCode === shortCode);
res.json({
shortCode,
originalUrl: longUrl,
totalClicks: clicks.length,
recentClicks: clicks.slice(-10) // 最近 10 次点击
});
});
app.listen(3000, () => {
console.log('Short URL service running on port 3000');
});
优化方案:
- 缓存层:使用 Redis 缓存热点短链接,减少数据库压力
- 分布式 ID 生成:使用 Snowflake 算法生成全局唯一 ID
- 分片存储:按短代码前缀分片存储,提高查询效率
- CDN 加速:静态资源使用 CDN,API 使用负载均衡
2. 负载均衡策略
问题:在高并发场景下,如何设计负载均衡方案?
回答:
负载均衡层次:
- DNS 负载均衡:通过 DNS 解析返回不同 IP
- 硬件负载均衡:F5、A10 等专用设备
- 软件负载均衡:Nginx、HAProxy、LVS
- 应用层负载均衡:Spring Cloud、Dubbo 等框架内置
Nginx 负载均衡配置示例:
# 定义上游服务器组
upstream backend_servers {
# 负载均衡策略
# 1. 轮询(默认)
# server 192.168.1.10:8080;
# server 192.168.1.11:8080;
# 2. 加权轮询
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
# 3. IP 哈希(保持会话)
# ip_hash;
# server 192.168.1.10:8080;
# server 192.168.1.11:8080;
# 4. 最少连接数
# least_conn;
# server 192.168.1.10:8080;
# server 192.168.1.11:8080;
# 健康检查
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 超时设置
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
负载均衡算法对比:
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 轮询 | 简单公平 | 不考虑服务器性能 | 服务器性能相近 |
| 加权轮询 | 考虑服务器性能 | 配置复杂 | 服务器性能差异大 |
| IP 哈希 | 保持会话一致性 | 可能导致负载不均 | 需要会话保持的场景 |
| 最少连接数 | 动态分配负载 | 需要统计连接数 | 长连接场景 |
| 响应时间 | 优先选择快的服务器 | 需要额外监控 | 对响应时间敏感 |
第四部分:DevOps 与部署
1. Docker 基础
问题:编写一个 Dockerfile,将 Node.js 应用容器化。
回答:
# 使用官方 Node.js 镜像作为基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖(使用 ci 确保版本一致)
RUN npm ci --only=production
# 复制应用源代码
COPY . .
# 暴露应用端口
EXPOSE 3000
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1
# 启动应用
CMD ["node", "server.js"]
优化后的 Dockerfile(多阶段构建):
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # 如果有构建步骤
# 运行阶段
FROM node:18-alpine
WORKDIR /app
# 从构建阶段复制依赖
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
# 复制构建产物(如果有)
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/server.js ./
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Docker Compose 配置:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
- redis
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
2. CI/CD 流程
问题:设计一个完整的 CI/CD 流程,使用 GitHub Actions。
回答:
# .github/workflows/deploy.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
run: npm test
- name: Build application
run: npm run build
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
security-scan:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Run dependency review
uses: actions/dependency-review-action@v3
build-and-push:
runs-on: ubuntu-latest
needs: [test, security-scan]
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myapp:${{ github.sha }}
myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
runs-on: ubuntu-latest
needs: build-and-push
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d --remove-orphans
docker system prune -f
第五部分:高级面试题
1. 微服务架构设计
问题:设计一个电商系统的微服务架构,并说明服务间通信方式。
回答:
微服务划分:
API Gateway
├── 用户服务 (User Service)
├── 商品服务 (Product Service)
├── 订单服务 (Order Service)
├── 支付服务 (Payment Service)
├── 库存服务 (Inventory Service)
├── 推荐服务 (Recommendation Service)
└── 通知服务 (Notification Service)
服务间通信:
- 同步通信:REST API、gRPC
- 异步通信:消息队列(RabbitMQ、Kafka)
示例:订单服务调用库存服务(gRPC):
// inventory.proto
syntax = "proto3";
package inventory;
service InventoryService {
rpc CheckStock(CheckStockRequest) returns (CheckStockResponse);
rpc ReduceStock(ReduceStockRequest) returns (ReduceStockResponse);
}
message CheckStockRequest {
string product_id = 1;
int32 quantity = 2;
}
message CheckStockResponse {
bool in_stock = 1;
int32 available_quantity = 2;
}
message ReduceStockRequest {
string product_id = 1;
int32 quantity = 2;
string order_id = 3;
}
message ReduceStockResponse {
bool success = 1;
string message = 2;
}
订单服务客户端代码:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// 加载 proto 文件
const packageDefinition = protoLoader.loadSync(
path.join(__dirname, 'inventory.proto'),
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);
const inventoryProto = grpc.loadPackageDefinition(packageDefinition).inventory;
// 创建客户端
const client = new inventoryProto.InventoryService(
'inventory-service:50051',
grpc.credentials.createInsecure()
);
// 检查库存
function checkStock(productId, quantity) {
return new Promise((resolve, reject) => {
client.CheckStock({ product_id: productId, quantity }, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
// 扣减库存
function reduceStock(productId, quantity, orderId) {
return new Promise((resolve, reject) => {
client.ReduceStock({ product_id: productId, quantity, order_id: orderId }, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
// 在订单创建流程中使用
async function createOrder(orderData) {
const { productId, quantity } = orderData;
// 1. 检查库存
const stockCheck = await checkStock(productId, quantity);
if (!stockCheck.in_stock) {
throw new Error('库存不足');
}
// 2. 创建订单(数据库操作)
const order = await createOrderInDB(orderData);
// 3. 扣减库存
try {
await reduceStock(productId, quantity, order.id);
} catch (error) {
// 回滚订单
await deleteOrder(order.id);
throw error;
}
// 4. 发送通知(异步)
await sendNotification(order.id, '订单创建成功');
return order;
}
2. 性能优化
问题:如何定位和解决 Node.js 应用的性能瓶颈?
回答:
性能分析工具:
Node.js 内置工具:
--inspect标志配合 Chrome DevToolsprocess.memoryUsage()监控内存perf_hooks模块
第三方工具:
- Clinic.js
- 0x
- PM2 监控
性能优化策略:
1. CPU 瓶颈定位与优化:
// 使用 Clinic.js 分析
// 安装:npm install -g clinic
// 运行:clinic doctor --on-port 'autocannon localhost:3000' -- node server.js
// 代码示例:避免阻塞事件循环
// ❌ 错误示例:同步操作阻塞事件循环
app.get('/heavy-computation', (req, res) => {
const result = heavyComputation(); // 同步耗时操作
res.json({ result });
});
// ✅ 正确示例:使用 Worker Threads
const { Worker } = require('worker_threads');
const path = require('path');
app.get('/heavy-computation', async (req, res) => {
try {
const result = await runInWorker('./heavy-computation-worker.js');
res.json({ result });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
function runInWorker(workerFile) {
return new Promise((resolve, reject) => {
const worker = new Worker(path.resolve(workerFile));
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
2. 内存泄漏检测:
// 使用 heapdump 生成堆快照
const heapdump = require('heapdump');
// 在特定条件下生成快照
app.get('/debug/heapdump', (req, res) => {
const filename = `/tmp/heapsnapshot-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, (err, filename) => {
if (err) {
res.status(500).json({ error: err.message });
} else {
res.json({ message: 'Heap dump created', filename });
}
});
});
// 使用 Chrome DevTools 分析:
// 1. 访问 chrome://inspect
// 2. 点击 "Open dedicated DevTools for Node"
// 3. 在 Memory 标签页选择 "Heap snapshot"
// 4. 对比两次快照,查找内存增长的对象
3. 数据库查询优化:
// ❌ 低效查询:N+1 查询问题
app.get('/users-with-posts', async (req, res) => {
const users = await User.find(); // 1 次查询
const usersWithPosts = await Promise.all(
users.map(async (user) => {
const posts = await Post.find({ userId: user.id }); // N 次查询
return { ...user, posts };
})
);
res.json(usersWithPosts);
});
// ✅ 高效查询:使用聚合或 JOIN
app.get('/users-with-posts', async (req, res) => {
// MongoDB 聚合
const usersWithPosts = await User.aggregate([
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'userId',
as: 'posts'
}
}
]);
res.json(usersWithPosts);
});
// SQL 示例
const result = await db.query(`
SELECT u.*, p.*
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
`);
4. 缓存策略:
const redis = require('redis');
const client = redis.createClient();
// Redis 缓存中间件
function cache(duration) {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
// 尝试从缓存获取
const cached = await client.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// 重写 res.json
const originalJson = res.json.bind(res);
res.json = (data) => {
// 存入缓存
client.setex(key, duration, JSON.stringify(data));
originalJson(data);
};
next();
} catch (error) {
next();
}
};
}
// 使用缓存
app.get('/api/products', cache(300), async (req, res) => {
const products = await Product.find().limit(100);
res.json(products);
});
第六部分:行为面试题
1. 项目经验
问题:描述一个你遇到的最具挑战性的技术问题,以及你是如何解决的。
回答框架:
- 背景:项目背景和遇到的问题
- 挑战:具体的技术难点
- 解决方案:采取的措施和技术选型
- 结果:最终效果和学到的经验
示例回答:
“在开发一个实时聊天应用时,我们遇到了高并发下的消息延迟问题。当在线用户超过 5000 时,消息延迟从平均 50ms 上升到 500ms。
挑战分析:
- WebSocket 连接数过多导致服务器负载过高
- 消息广播机制效率低下
- 数据库写入成为瓶颈
解决方案:
- 引入 Redis Pub/Sub:将消息广播改为发布/订阅模式,减少服务器间通信
- 消息分片:按聊天室分片,每个分片独立处理
- 批量写入:将数据库写入从实时改为每 100ms 批量写入
- 水平扩展:使用 PM2 集群模式,部署 4 个实例
结果:
- 延迟降低到 30ms 以下
- 支持 20000+ 并发用户
- 服务器资源占用降低 40%
经验总结:性能优化需要从架构层面思考,不能只关注代码层面。”
2. 团队协作
问题:当与同事在技术方案上有分歧时,你会如何处理?
回答要点:
- 倾听理解:先理解对方的观点和理由
- 数据驱动:用数据和事实说话,而不是主观判断
- 寻求共识:寻找双方都能接受的折中方案
- 实验验证:必要时通过小规模实验验证方案
- 尊重决定:团队决策后全力执行,即使个人有不同意见
第七部分:面试准备建议
1. 知识体系构建
前端:
- HTML5/CSS3 核心特性
- JavaScript 异步编程、闭包、原型链
- 至少掌握一个框架(React/Vue/Angular)
- 状态管理、路由、表单处理
- 性能优化、浏览器渲染原理
后端:
- 至少精通一门语言(Node.js/Python/Java)
- RESTful API 设计
- 数据库设计与优化(SQL + NoSQL)
- 缓存策略(Redis)
- 消息队列(RabbitMQ/Kafka)
DevOps:
- Docker 容器化
- CI/CD 流程
- Linux 基础命令
- 监控与日志
2. 项目准备
准备 3-5 个完整的项目经历,涵盖:
- 项目背景和目标
- 技术选型和架构设计
- 核心功能实现
- 遇到的挑战和解决方案
- 项目成果和数据指标
3. 刷题建议
算法题:
- LeetCode 热门 100 题
- 剑指 Offer
- 重点:数组、字符串、链表、树、动态规划
系统设计题:
- 设计 Twitter
- 设计短链接服务
- 设计电商系统
- 设计推荐系统
4. 面试技巧
技术面试:
- 清晰表达思路,先讲整体方案再讲细节
- 遇到不会的问题,展示思考过程
- 主动提及相关的优化和扩展
行为面试:
- 使用 STAR 法则(Situation, Task, Action, Result)
- 准备具体的例子和数据
- 展示学习能力和成长意愿
结语
全栈开发工程师的面试准备是一个系统工程,需要扎实的技术基础、丰富的项目经验和良好的沟通能力。通过本文提供的题库和示例,希望你能:
- 系统复习:查漏补缺,完善知识体系
- 深入理解:不仅记住答案,更要理解原理
- 灵活应用:将知识应用到实际项目中
- 自信应对:在面试中展示真实的实力
记住,面试是双向选择的过程。除了展示技术能力,也要了解公司的技术栈、团队文化和项目挑战。祝你在全栈开发工程师的面试中取得成功!
