引言:全栈开发工程师的核心竞争力

全栈开发工程师(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>&copy; 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 操作并发执行,而不需要为每个操作创建线程。事件循环分为多个阶段,每个阶段处理特定类型的任务。

事件循环的主要阶段:

  1. Timers:执行 setTimeoutsetInterval 的回调。
  2. I/O Callbacks:执行几乎所有的回调,除了 close、timers 和 setImmediate。
  3. Idle, Prepare:仅内部使用。
  4. Poll:检索新的 I/O 事件,执行 I/O 相关的回调。
  5. Check:执行 setImmediate 的回调。
  6. 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)。

回答:

核心需求分析:

  1. 短链接生成:将长 URL 转换为短 URL(如 https://example.com/abc123
  2. 重定向:访问短链接时跳转到原始 URL
  3. 高可用性:支持高并发访问
  4. 数据统计:记录点击次数等信息

系统架构设计:

客户端 → 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');
});

优化方案:

  1. 缓存层:使用 Redis 缓存热点短链接,减少数据库压力
  2. 分布式 ID 生成:使用 Snowflake 算法生成全局唯一 ID
  3. 分片存储:按短代码前缀分片存储,提高查询效率
  4. CDN 加速:静态资源使用 CDN,API 使用负载均衡

2. 负载均衡策略

问题:在高并发场景下,如何设计负载均衡方案?

回答:

负载均衡层次:

  1. DNS 负载均衡:通过 DNS 解析返回不同 IP
  2. 硬件负载均衡:F5、A10 等专用设备
  3. 软件负载均衡:Nginx、HAProxy、LVS
  4. 应用层负载均衡: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)

服务间通信:

  1. 同步通信:REST API、gRPC
  2. 异步通信:消息队列(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 应用的性能瓶颈?

回答:

性能分析工具:

  1. Node.js 内置工具

    • --inspect 标志配合 Chrome DevTools
    • process.memoryUsage() 监控内存
    • perf_hooks 模块
  2. 第三方工具

    • 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. 项目经验

问题:描述一个你遇到的最具挑战性的技术问题,以及你是如何解决的。

回答框架:

  1. 背景:项目背景和遇到的问题
  2. 挑战:具体的技术难点
  3. 解决方案:采取的措施和技术选型
  4. 结果:最终效果和学到的经验

示例回答:

“在开发一个实时聊天应用时,我们遇到了高并发下的消息延迟问题。当在线用户超过 5000 时,消息延迟从平均 50ms 上升到 500ms。

挑战分析

  • WebSocket 连接数过多导致服务器负载过高
  • 消息广播机制效率低下
  • 数据库写入成为瓶颈

解决方案

  1. 引入 Redis Pub/Sub:将消息广播改为发布/订阅模式,减少服务器间通信
  2. 消息分片:按聊天室分片,每个分片独立处理
  3. 批量写入:将数据库写入从实时改为每 100ms 批量写入
  4. 水平扩展:使用 PM2 集群模式,部署 4 个实例

结果

  • 延迟降低到 30ms 以下
  • 支持 20000+ 并发用户
  • 服务器资源占用降低 40%

经验总结:性能优化需要从架构层面思考,不能只关注代码层面。”


2. 团队协作

问题:当与同事在技术方案上有分歧时,你会如何处理?

回答要点:

  1. 倾听理解:先理解对方的观点和理由
  2. 数据驱动:用数据和事实说话,而不是主观判断
  3. 寻求共识:寻找双方都能接受的折中方案
  4. 实验验证:必要时通过小规模实验验证方案
  5. 尊重决定:团队决策后全力执行,即使个人有不同意见

第七部分:面试准备建议

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)
  • 准备具体的例子和数据
  • 展示学习能力和成长意愿

结语

全栈开发工程师的面试准备是一个系统工程,需要扎实的技术基础、丰富的项目经验和良好的沟通能力。通过本文提供的题库和示例,希望你能:

  1. 系统复习:查漏补缺,完善知识体系
  2. 深入理解:不仅记住答案,更要理解原理
  3. 灵活应用:将知识应用到实际项目中
  4. 自信应对:在面试中展示真实的实力

记住,面试是双向选择的过程。除了展示技术能力,也要了解公司的技术栈、团队文化和项目挑战。祝你在全栈开发工程师的面试中取得成功!