重新学习前端之其他

5 阅读23分钟

其他

一、前端工程化

1. 什么是前端工程化?如何实施前端工程化?

定义

前端工程化是指将软件工程的理念、方法和工具应用到前端开发中,通过标准化、自动化、模块化的手段,提升前端项目的开发效率、代码质量、可维护性和团队协作能力。

核心理念

  • 一切皆模块:将 CSS、JS、图片、字体等资源都视为模块进行管理
  • 一切皆可自动化:构建、测试、部署等流程尽可能自动化
  • 标准化规范:统一的代码规范、提交规范、分支管理规范
  • 可度量指标:代码质量、构建速度、测试覆盖率等指标可量化

实施步骤

  1. 技术选型:选择适合的构建工具(Vite/Webpack/Rollup)、包管理器、代码规范工具
  2. 规范制定:ESLint、Prettier、StyleLint、CommitLint
  3. 脚手架搭建:创建项目模板,统一项目结构
  4. 构建流程:配置打包、压缩、代码分割、Tree Shaking
  5. 自动化测试:单元测试、集成测试、E2E 测试
  6. CI/CD 流程:自动化构建、测试、部署
  7. 监控体系:错误监控、性能监控、埋点统计

核心工具链

类别工具作用
构建工具Vite/Webpack模块打包、代码转换
代码规范ESLint代码质量检查
代码格式化Prettier统一代码风格
提交规范Husky + CommitLintGit 提交信息规范
包管理npm/yarn/pnpm依赖管理
测试框架Jest/Vitest单元测试
E2E 测试Cypress/Playwright端到端测试
CI/CDGitHub Actions/Jenkins持续集成/部署

常见误区

  • 工程化不只是使用 Webpack 或 Vite,而是一个完整的体系
  • 不是工具越多越好,要根据团队和项目规模选择合适的方案
  • 工程化不是一次性工作,需要持续优化和维护

2. 前端工程化的目标和实施步骤

目标

  1. 提高开发效率:通过脚手架、代码生成、热更新等手段减少重复工作
  2. 提升代码质量:通过代码规范、静态检查、自动化测试保证代码质量
  3. 优化构建性能:缩短构建时间,提升开发体验
  4. 标准化流程:统一团队开发流程,降低协作成本
  5. 可维护性:模块化、组件化设计,便于后期维护和扩展

实施步骤

需求分析 → 技术选型 → 规范制定 → 脚手架搭建 → 构建配置 → 测试体系 → CI/CD → 监控体系 → 持续优化

各阶段关键点

  • 需求分析:项目规模、团队规模、性能要求、发布频率
  • 技术选型:根据需求选择合适的工具链,避免过度设计
  • 规范制定:编码规范、Git 规范、代码审查规范
  • 脚手架搭建:统一项目模板,包含目录结构、基础配置、通用组件
  • 构建配置:开发环境快速热更新,生产环境最优打包
  • 测试体系:分层测试策略,单元测试 + 集成测试 + E2E 测试
  • CI/CD:自动化构建、测试、部署流水线
  • 监控体系:前端错误监控、性能监控、用户行为分析

3. 前端工程化的核心内容

前端工程化的核心内容包括以下几个维度:

1. 模块化

  • ES Modules / CommonJS 模块化规范
  • CSS Modules / Styled Components 样式模块化
  • 资源模块化(图片、字体等)

2. 组件化

  • UI 组件库建设
  • 业务组件抽象
  • 组件文档与 Storybook

3. 规范化

  • 代码规范:ESLint、Prettier
  • 样式规范:StyleLint
  • 提交规范:Conventional Commits
  • 文档规范:JSDoc、TypeDoc

4. 自动化

  • 自动化构建:Webpack/Vite 配置
  • 自动化测试:Jest/Vitest/Cypress
  • 自动化部署:CI/CD 流水线
  • 自动化检查:Husky 预提交钩子

5. 性能优化

  • 构建性能:缓存、并行、增量构建
  • 运行时性能:代码分割、懒加载、Tree Shaking
  • 资源优化:图片压缩、字体子集化

6. 可观测性

  • 错误监控:Sentry
  • 性能监控:Lighthouse、Web Vitals
  • 埋点统计:用户行为分析

4. 脚手架(Scaffolding)

定义

脚手架是用于快速创建项目基础结构的工具,通过模板化和自动化减少项目初始化时的重复工作。

核心功能

  1. 项目模板生成:根据预设模板生成项目目录结构
  2. 配置文件生成:生成 ESLint、Prettier、TypeScript、构建工具等配置
  3. 依赖安装:自动安装项目所需的依赖包
  4. 初始化脚本:运行初始化脚本完成额外配置

实现方式

  • 使用现有工具:Create React App、Vite、Vue CLI、Create Next App
  • 自定义脚手架:基于 Commander.js、Inquirer.js、Chalk 等开发

自定义脚手架示例

#!/usr/bin/env node
// cli.js
const { Command } = require('commander');
const inquirer = require('inquirer');
const fs = require('fs-extra');
const path = require('path');
const { execSync } = require('child_process');

const program = new Command();

program
  .name('create-my-app')
  .description('创建一个新的前端项目')
  .argument('<project-name>', '项目名称')
  .action(async (projectName) => {
    const projectPath = path.join(process.cwd(), projectName);

    // 检查目录是否存在
    if (fs.existsSync(projectPath)) {
      console.error(`目录 ${projectName} 已存在`);
      process.exit(1);
    }

    // 询问项目配置
    const answers = await inquirer.prompt([
      {
        type: 'list',
        name: 'framework',
        message: '选择框架:',
        choices: ['React', 'Vue', 'Next.js'],
      },
      {
        type: 'list',
        name: 'language',
        message: '选择语言:',
        choices: ['TypeScript', 'JavaScript'],
      },
    ]);

    // 复制模板
    const templatePath = path.join(__dirname, 'templates', answers.framework);
    fs.copySync(templatePath, projectPath);

    // 替换模板变量
    const pkgPath = path.join(projectPath, 'package.json');
    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
    pkg.name = projectName;
    fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));

    // 安装依赖
    console.log('正在安装依赖...');
    execSync('npm install', { cwd: projectPath, stdio: 'inherit' });

    console.log(`项目 ${projectName} 创建成功!`);
  });

program.parse();

常见误区

  • 脚手架不只是复制文件,还应该包含合理的默认配置
  • 不要过度设计,保持简单可扩展
  • 模板需要定期更新,跟随技术生态发展

5. 自动化构建

定义

自动化构建是将源代码转换为可在生产环境运行的代码的过程,包括代码编译、压缩、优化等步骤。

核心流程

源码 → 预处理(TS/SCSS/Less) → 代码检查 → 模块打包 → 代码压缩 → 资源优化 → 产物输出

构建工具对比

特性WebpackViteRollup
开发服务器webpack-dev-server基于 esbuild不内置
构建速度较慢极快(esbuild 预构建)较快
模块格式多种ESMESM 为主
适用场景大型复杂项目中小型项目库/框架打包
配置复杂度
生态丰富度最丰富快速增长中一般

Webpack 核心配置

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 小于 8KB 转为 base64
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css',
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },
      },
    },
  },
};

构建优化策略

  • 缓存:Webpack 5 持久化缓存、Vite 依赖预构建缓存
  • 并行处理:thread-loader、esbuild
  • Tree Shaking:消除未使用的代码
  • 代码分割:动态 import、路由懒加载
  • 增量构建:只构建变更的文件

6. 自动化测试

测试分层策略

        / \
       / E2E \          ← 端到端测试(Cypress/Playwright)
      /-------\
     / Integration\     ← 集成测试(组件/模块间交互)
    /--------------\
   /   Unit Tests   \   ← 单元测试(函数/组件)
  /------------------\

测试金字塔

  • 单元测试(70%):测试单个函数或组件的逻辑
  • 集成测试(20%):测试多个模块之间的交互
  • E2E 测试(10%):模拟真实用户操作流程

Jest 单元测试示例

// utils/formatDate.js
export function formatDate(date, format = 'YYYY-MM-DD') {
  const d = new Date(date);
  if (isNaN(d.getTime())) {
    throw new Error('Invalid date');
  }

  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');

  return format
    .replace('YYYY', year)
    .replace('MM', month)
    .replace('DD', day);
}

// utils/formatDate.test.js
import { formatDate } from './formatDate';

describe('formatDate', () => {
  test('should format date with default format', () => {
    expect(formatDate('2024-01-15')).toBe('2024-01-15');
  });

  test('should format date with custom format', () => {
    expect(formatDate('2024-01-15', 'DD/MM/YYYY')).toBe('15/01/2024');
  });

  test('should throw error for invalid date', () => {
    expect(() => formatDate('invalid')).toThrow('Invalid date');
  });
});

React Testing Library 示例

// Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Counter } from './Counter';

describe('Counter', () => {
  test('renders initial count', () => {
    render(<Counter />);
    expect(screen.getByText('0')).toBeInTheDocument();
  });

  test('increments count on button click', async () => {
    render(<Counter />);
    const button = screen.getByRole('button', { name: /increment/i });
    fireEvent.click(button);
    expect(await screen.findByText('1')).toBeInTheDocument();
  });
});

Cypress E2E 测试示例

// cypress/e2e/login.cy.js
describe('Login Flow', () => {
  it('should login successfully with valid credentials', () => {
    cy.visit('/login');
    cy.get('[data-testid="username-input"]').type('admin');
    cy.get('[data-testid="password-input"]').type('password123');
    cy.get('[data-testid="login-button"]').click();
    cy.url().should('include', '/dashboard');
    cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
  });
});

最佳实践

  • 使用有意义的测试描述,遵循 AAA 模式(Arrange-Act-Assert)
  • 优先测试业务逻辑,而非实现细节
  • 使用 Mock 隔离外部依赖
  • 保持测试独立,不依赖测试执行顺序
  • 定期维护测试,避免测试腐化

7. 自动化部署与 CI/CD

CI/CD 概念

  • CI(持续集成):代码合并到主分支时自动构建和测试
  • CD(持续交付):通过自动化测试后,可手动部署到生产环境
  • CD(持续部署):通过所有测试后自动部署到生产环境

GitHub Actions 配置示例

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Upload artifacts
        uses: actions/upload-pages-artifact@v3
        with:
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to production
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          projectName: my-app
          directory: dist/

部署策略

策略说明适用场景
蓝绿部署两个完全相同的环境交替使用需要快速回滚的场景
金丝雀发布逐步将流量导向新版本降低发布风险
滚动更新逐台替换服务器Kubernetes 环境
A/B 测试同时运行两个版本进行对比需要验证功能效果

8. 代码质量与代码审查

代码质量保障体系

  1. 静态分析:ESLint、StyleLint、TypeScript 类型检查
  2. 代码格式化:Prettier 统一代码风格
  3. 提交前检查:Husky + lint-staged
  4. 代码审查:Pull Request 流程
  5. 质量门禁:SonarQube 代码质量分析

Husky 配置示例

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss}": [
      "stylelint --fix",
      "prettier --write"
    ]
  }
}

代码审查清单

  • 代码是否符合编码规范?
  • 是否有潜在的安全漏洞?
  • 错误处理是否完善?
  • 是否有适当的注释和文档?
  • 性能是否合理?有无性能瓶颈?
  • 是否有充分的测试覆盖?
  • 代码是否可维护、可扩展?

常见误区

  • 代码审查不是找茬,而是知识共享和质量提升
  • 不要只关注格式问题,应关注设计和逻辑
  • 小步提交比大改动更容易审查
  • 及时审查,避免上下文丢失

二、性能优化

9. 前端性能优化的核心指标

Web Vitals 核心指标

指标全称含义优秀标准
LCPLargest Contentful Paint最大内容绘制时间≤ 2.5s
FIDFirst Input Delay首次输入延迟≤ 100ms
CLSCumulative Layout Shift累积布局偏移≤ 0.1
INPInteraction to Next Paint交互到下次绘制≤ 200ms
TTFBTime to First Byte首字节时间≤ 800ms
FCPFirst Contentful Paint首次内容绘制≤ 1.8s

指标测量工具

  • Lighthouse:Chrome DevTools 内置,综合评分
  • WebPageTest:详细的性能分析报告
  • Chrome DevTools Performance:运行时性能分析
  • Performance APIwindow.performance 编程测量
// 测量页面性能指标
const performanceData = performance.getEntriesByType('navigation')[0];

console.log({
  ttfb: performanceData.responseStart - performanceData.requestStart,
  domReady: performanceData.domContentLoadedEventEnd - performanceData.navigationStart,
  loadComplete: performanceData.loadEventEnd - performanceData.navigationStart,
});

// 使用 PerformanceObserver 监听 Web Vitals
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log(`${entry.name}: ${entry.startTime}ms`);
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });

10. 网页性能优化体系与多维度优化策略

性能优化体系

性能优化
├── 加载优化
│   ├── 减少请求数量
│   ├── 减小资源体积
│   └── 优化请求策略
├── 渲染优化
│   ├── 关键渲染路径优化
│   ├── 避免布局抖动
│   └── 减少重绘重排
├── 运行时优化
│   ├── JavaScript 执行优化
│   ├── 内存管理
│   └── 渲染帧优化
└── 网络优化
    ├── HTTP/2 多路复用
    ├── CDN 加速
    └── 缓存策略

多维度优化策略

HTML 优化

  • 减少 DOM 节点数量,避免深层嵌套
  • 使用语义化标签,便于浏览器优化
  • 减少 inline script/style,避免阻塞渲染
  • 使用 deferasync 加载脚本
<!-- 推荐的脚本加载方式 -->
<script defer src="/app.js"></script>  <!-- DOM 解析完成后执行 -->
<script async src="/analytics.js"></script> <!-- 异步加载,不阻塞 -->

CSS 优化

  • 关键 CSS 内联,非关键 CSS 异步加载
  • 避免使用高成本的 CSS 属性(如 box-shadowfilter
  • 使用 CSS 变量减少重复代码
  • 避免深层选择器嵌套
<!-- 关键 CSS 内联 -->
<style>
  /* 首屏关键样式 */
  .header { /* ... */ }
  .hero { /* ... */ }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>

JavaScript 优化

  • 代码分割和懒加载
  • 防抖节流优化高频事件
  • 使用 requestAnimationFrame 优化动画
  • 避免同步 XHR 请求
// 防抖函数
function debounce(fn, delay = 300) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流函数
function throttle(fn, delay = 300) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

11. 首屏加载优化方案

问题分析

首屏加载慢的原因:

  • 资源文件过大(JS/CSS/图片)
  • 请求数量过多
  • 渲染阻塞
  • 网络延迟
  • 服务端响应慢

优化方案

1. 资源优化

// 路由懒加载 - React
const Dashboard = React.lazy(() => import('./Dashboard'));
const Settings = React.lazy(() => import('./Settings'));

// 路由懒加载 - Vue
const Dashboard = () => import('./Dashboard.vue');
const Settings = () => import('./Settings.vue');

// 图片懒加载
<img loading="lazy" src="image.jpg" alt="description" />

// 使用 IntersectionObserver 实现自定义图片懒加载
class ImageLazyLoader {
  constructor() {
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.removeAttribute('data-src');
          this.observer.unobserve(img);
        }
      });
    });
  }

  init() {
    document.querySelectorAll('img[data-src]').forEach((img) => {
      this.observer.observe(img);
    });
  }
}

2. 代码分割

// Webpack 动态导入 - 指定 chunk 名称
const ChartComponent = () =>
  import(/* webpackChunkName: "charts" */ './ChartComponent');

// 预加载关键资源
<link rel="preload" href="/critical-font.woff2" as="font" crossorigin />
<link rel="prefetch" href="/next-page-bundle.js" as="script" />

3. 服务端渲染(SSR)

// Next.js 服务端渲染
export async function getServerSideProps() {
  const data = await fetch('https://api.example.com/data');
  return { props: { data } };
}

// Nuxt.js 服务端渲染
export default {
  async asyncData({ $axios }) {
    const data = await $axios.get('/api/data');
    return { data };
  },
};

4. 缓存策略

// Service Worker 缓存策略
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      if (cachedResponse) return cachedResponse;

      return fetch(event.request).then((response) => {
        const cache = caches.open('v1');
        cache.put(event.request, response.clone());
        return response;
      });
    })
  );
});

5. HTTP 优化

  • 启用 HTTP/2 多路复用
  • 启用 Gzip/Brotli 压缩
  • 设置合理的缓存头(Cache-Control)
  • 使用 CDN 分发静态资源

12. 图片懒加载

定义

图片懒加载是指图片在进入可视区域之前不加载,进入可视区域后才开始加载的技术,可以显著减少首屏加载时间和带宽消耗。

实现原理

  1. 图片初始 src 设置为占位图或为空,真实地址存储在 data-src 属性中
  2. 监听滚动事件或使用 IntersectionObserver 检测图片是否进入视口
  3. 图片进入视口后,将 data-src 赋值给 src 触发加载

实现方式

方式一:IntersectionObserver(推荐)

// 现代浏览器方案
function lazyLoadImages() {
  const imageObserver = new IntersectionObserver(
    (entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          if (img.dataset.srcset) {
            img.srcset = img.dataset.srcset;
          }
          img.classList.remove('lazy');
          observer.unobserve(img);
        }
      });
    },
    {
      rootMargin: '50px', // 提前 50px 开始加载
      threshold: 0.01,
    }
  );

  document.querySelectorAll('img[data-src]').forEach((img) => {
    imageObserver.observe(img);
  });
}

// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', lazyLoadImages);

方式二:滚动事件监听(兼容方案)

function lazyLoadImagesFallback() {
  const images = document.querySelectorAll('img[data-src]');

  function checkImages() {
    const windowHeight = window.innerHeight;
    const scrollTop = window.scrollY || document.documentElement.scrollTop;

    images.forEach((img, index) => {
      const rect = img.getBoundingClientRect();
      if (rect.top < windowHeight + 100) {
        img.src = img.dataset.src;
        images[index] = null; // 标记已处理
      }
    });
  }

  window.addEventListener('scroll', checkImages);
  window.addEventListener('resize', checkImages);
  checkImages(); // 初始检查
}

方式三:原生 loading="lazy" 属性

<!-- HTML 原生支持,无需 for modern browsers -->
<img src="image.jpg" loading="lazy" alt="description" />
<img src="thumbnail.jpg" loading="eager" alt="critical image" />

优点

  • 减少首屏加载资源数量
  • 节省带宽和服务器压力
  • 提升页面加载速度和用户体验
  • 对 SEO 友好(现代搜索引擎支持懒加载)

注意事项

  • LCP 图片不应懒加载,否则影响核心指标
  • 设置合理的预加载边界(rootMargin)
  • 提供占位图避免布局偏移
  • 考虑 SEO 影响,重要图片不要懒加载

13. CDN 加速

定义

CDN(Content Delivery Network,内容分发网络)是通过在全球范围内部署缓存服务器,将内容分发到离用户最近的节点,从而加速内容传输的技术。

工作原理

用户请求 → DNS 解析 → 选择最近 CDN 节点 → 节点返回缓存内容
                                       ↓ (缓存未命中)
                                   回源站获取 → 缓存 → 返回用户

配置示例

// Vercel CDN 配置 - vercel.json
{
  "headers": [
    {
      "source": "/static/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    },
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=3600, s-maxage=86400"
        }
      ]
    }
  ]
}

优化策略

  • 静态资源设置长期缓存(带 hash 文件名)
  • 动态内容设置短期缓存
  • 启用 HTTP/2 和 Brotli 压缩
  • 配置合理的缓存策略(Cache-Control)
  • 使用 CDN 预热功能提前缓存热门资源

14. 减少 HTTP 请求的方法

策略汇总

方法说明适用场景
雪碧图(CSS Sprite)多张小图合并为一张大图图标、小装饰图
代码合并将多个 JS/CSS 文件合并构建时自动处理
Base64 编码小文件转为 base64 内联小于 8KB 的图片/字体
内联关键资源关键 CSS/JS 直接内联首屏关键样式
HTTP/2 多路复用单个连接并行请求现代浏览器
资源预加载preload/prefetch 预加载关键资源提前加载

雪碧图示例

// SCSS 实现雪碧图
.icon {
  background-image: url('/sprites/icons.png');
  background-size: 200px 200px;

  &-home {
    width: 24px;
    height: 24px;
    background-position: 0 0;
  }

  &-user {
    width: 24px;
    height: 24px;
    background-position: -24px 0;
  }

  &-settings {
    width: 24px;
    height: 24px;
    background-position: -48px 0;
  }
}

15. 虚拟 DOM 的优缺点

定义

虚拟 DOM(Virtual DOM)是用 JavaScript 对象描述真实 DOM 结构的抽象层,通过 Diff 算法对比新旧虚拟 DOM,最小化真实 DOM 操作。

工作原理

状态变化 → 创建新虚拟 DOM → Diff 算法对比 → 计算最小变更 → 批量更新真实 DOM

优点

优点说明
性能优化批量更新减少 DOM 操作,避免频繁重排
跨平台抽象层使框架可渲染到不同平台(Web、Native)
开发体验声明式编程,无需手动操作 DOM
可控更新精确控制哪些组件需要重新渲染

缺点

缺点说明
首次渲染慢需要额外创建虚拟 DOM 对象
内存占用维护两份 DOM 树(虚拟+真实)
内存开销对于简单页面,虚拟 DOM 可能比直接操作更慢
Diff 复杂度O(n^3) 优化为 O(n) 但仍有一定开销
无法完全替代大量数据时仍需手动优化(虚拟列表)

适用场景

  • 适合:频繁更新、复杂交互的中大型应用
  • 不适合:简单静态页面、极致性能要求的场景

16. 长列表性能优化方案

问题分析

长列表渲染大量 DOM 节点会导致:

  • 内存占用过高
  • 初次渲染慢
  • 滚动卡顿
  • 频繁 GC

解决方案:虚拟列表

虚拟列表只渲染可视区域内的元素,通过动态计算偏移量模拟完整列表。

React 实现

// VirtualList.jsx
import { useRef, useState, useMemo } from 'react';

function VirtualList({ items, itemHeight, renderItem }) {
  const containerRef = useRef(null);
  const [scrollTop, setScrollTop] = useState(0);

  const containerHeight = 600;
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount + 1, items.length);

  const visibleItems = useMemo(() => {
    return items.slice(startIndex, endIndex);
  }, [items, startIndex, endIndex]);

  const offsetY = startIndex * itemHeight;

  return (
    <div
      ref={containerRef}
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems.map((item, index) => (
            <div
              key={item.id}
              style={{ height: itemHeight, position: 'absolute', width: '100%' }}
            >
              {renderItem(item, startIndex + index)}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

export default VirtualList;

Vue 实现

<!-- VirtualList.vue -->
<template>
  <div ref="container" class="virtual-list-container" @scroll="onScroll">
    <div :style="{ height: totalHeight + 'px', position: 'relative' }">
      <div :style="{ transform: `translateY(${offsetY}px)` }">
        <div
          v-for="item in visibleItems"
          :key="item.id"
          :style="{ height: itemHeight + 'px' }"
          class="virtual-list-item"
        >
          <slot :item="item" />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const props = defineProps({
  items: { type: Array, required: true },
  itemHeight: { type: Number, required: true },
  containerHeight: { type: Number, default: 600 },
});

const container = ref(null);
const scrollTop = ref(0);

const visibleCount = computed(() =>
  Math.ceil(props.containerHeight / props.itemHeight)
);
const startIndex = computed(() =>
  Math.floor(scrollTop.value / props.itemHeight)
);
const endIndex = computed(() =>
  Math.min(startIndex.value + visibleCount.value + 1, props.items.length)
);
const visibleItems = computed(() =>
  props.items.slice(startIndex.value, endIndex.value)
);
const offsetY = computed(() => startIndex.value * props.itemHeight);
const totalHeight = computed(() => props.items.length * props.itemHeight);

function onScroll(e) {
  scrollTop.value = e.target.scrollTop;
}
</script>

优化策略总结

  • 使用虚拟列表只渲染可视区域
  • 避免不必要的重新渲染(React.memo、useMemo)
  • 使用 will-change 优化滚动性能
  • 预加载相邻区域数据(预渲染缓冲区)
  • 固定高度的列表性能优于动态高度

17. 如何避免不必要的重新渲染?

React 优化策略

// 1. React.memo - 组件级优化
const UserProfile = React.memo(function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
});

// 2. useMemo - 计算值缓存
function UserList({ users, filter }) {
  const filteredUsers = useMemo(() => {
    return users.filter((u) => u.name.includes(filter));
  }, [users, filter]); // 只在依赖变化时重新计算

  return (
    <ul>
      {filteredUsers.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// 3. useCallback - 函数引用缓存
function TodoApp() {
  const [todos, setTodos] = useState([]);

  const addTodo = useCallback(
    (text) => {
      setTodos((prev) => [...prev, { text, id: Date.now() }]);
    },
    [] // 空依赖,引用始终不变
  );

  return <TodoForm onAdd={addTodo} />;
}

// 4. 状态提升与拆分
function Form() {
  // 拆分为独立状态,避免无关更新
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  return (
    <>
      <NameInput value={name} onChange={setName} />
      <EmailInput value={email} onChange={setEmail} />
    </>
  );
}

Vue 优化策略

<!-- 1. v-memo - Vue 3.2+ 指令 -->
<div v-memo="[user.name, user.email]">
  <p>{{ user.name }}</p>
  <p>{{ user.email }}</p>
</div>

<!-- 2. 计算属性缓存 -->
<script setup>
import { computed, ref } from 'vue';

const users = ref([...]);
const filter = ref('');

// 计算属性自动缓存,依赖不变不重新计算
const filteredUsers = computed(() =>
  users.value.filter((u) => u.name.includes(filter.value))
);
</script>

<!-- 3. 组件级优化 -->
<script setup>
import { defineComponent, shallowRef } from 'vue';

// 使用 shallowRef 避免深层响应式
const largeData = shallowRef({ /* 大型对象 */ });
</script>

通用原则

  • 精确控制状态粒度,避免过度集中
  • 使用不可变数据,便于引用比较
  • 合理使用缓存机制(memo、computed)
  • 避免在渲染中创建新对象/函数
  • 使用 Profiler 工具定位性能瓶颈

18. 前端异常捕获与处理

异常捕获方式

1. try...catch

// 同步错误捕获
try {
  const data = JSON.parse(invalidJSON);
} catch (error) {
  console.error('JSON 解析失败:', error.message);
  // 上报错误
  reportError(error, { context: 'parseUserConfig' });
}

// async/await 错误处理
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('请求失败:', error);
    throw error; // 继续抛出,让调用方处理
  }
}

2. window.onerror

// 全局错误监听
window.onerror = function (message, source, lineno, colno, error) {
  const errorInfo = {
    message,
    source,
    lineno,
    colno,
    stack: error?.stack,
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: Date.now(),
  };

  // 上报到错误监控平台
  sendErrorReport(errorInfo);

  return false; // 返回 false 不阻止默认错误处理
};

3. unhandledrejection

// 未捕获的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
  const errorInfo = {
    message: event.reason?.message || String(event.reason),
    stack: event.reason?.stack,
    url: window.location.href,
    timestamp: Date.now(),
  };

  sendErrorReport(errorInfo);
  event.preventDefault(); // 阻止控制台输出
});

4. Vue 错误处理

// Vue 全局错误处理
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.config.errorHandler = (error, instance, info) => {
  console.error('Vue 错误:', error);
  console.error('错误信息:', info);

  sendErrorReport({
    message: error.message,
    stack: error.stack,
    component: instance?.$options?.name,
    lifecycle: info,
  });
};

5. React 错误边界

// React Error Boundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // 上报错误
    sendErrorReport({
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <DefaultErrorUI />;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary fallback={<CustomErrorPage />}>
  <App />
</ErrorBoundary>;

6. Source Map 错误还原

// 使用 source-map 库还原错误
const sourceMap = require('source-map');

async function parseError(errorStack) {
  const consumer = await new sourceMap.SourceMapConsumer(
    fs.readFileSync('app.js.map', 'utf-8')
  );

  // 解析错误堆栈中的位置
  const original = consumer.originalPositionFor({
    line: errorStack.line,
    column: errorStack.column,
  });

  console.log(`Original: ${original.source}:${original.line}:${original.column}`);
  consumer.destroy();
}

最佳实践

  • 多层级错误捕获:局部 try-catch + 全局监听
  • 区分业务错误和系统错误
  • 错误信息脱敏,不暴露敏感数据
  • 设置错误上报频率限制,避免刷屏
  • 配置错误告警规则,及时处理关键错误

19. 前端国际化(i18n)的实现

实现方案对比

方案适用场景优点缺点
静态 JSON 文件简单项目实现简单更新需重新发布
服务端下发多语言频繁更新实时生效依赖服务端
i18n 库中大型项目功能完善增加包体积
编译时替换单语言发布零运行时开销需多次构建

React + react-i18next 实现

// i18n.js 配置
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import en from './locales/en.json';
import zh from './locales/zh.json';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: { translation: en },
      zh: { translation: zh },
    },
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;
// locales/en.json
{
  "greeting": "Hello, {{name}}!",
  "items_count": "{{count}} item(s)",
  "login": {
    "title": "Sign In",
    "username": "Username",
    "password": "Password",
    "submit": "Login"
  }
}
// locales/zh.json
{
  "greeting": "你好,{{name}}!",
  "items_count": "{{count}} 个项目",
  "login": {
    "title": "登录",
    "username": "用户名",
    "password": "密码",
    "submit": "登录"
  }
}
// 组件中使用
import { useTranslation } from 'react-i18next';

function Greeting({ name }) {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t('greeting', { name })}</h1>
      <p>{t('items_count', { count: 5 })}</p>
    </div>
  );
}

Vue + vue-i18n 实现

// i18n.js 配置
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import zh from './locales/zh.json';

const i18n = createI18n({
  legacy: false, // 使用 Composition API 模式
  locale: 'zh',
  fallbackLocale: 'en',
  messages: { en, zh },
});

export default i18n;
<!-- 组件中使用 -->
<template>
  <div>
    <h1>{{ t('greeting', { name: userName }) }}</h1>
    <p>{{ t('login.title') }}</p>
  </div>
</template>

<script setup>
import { useI18n } from 'vue-i18n';

const { t, locale } = useI18n();

function switchLanguage(lang) {
  locale.value = lang;
  localStorage.setItem('locale', lang);
}
</script>

最佳实践

  • 使用命名空间组织翻译文件
  • 支持复数形式和上下文变量
  • 提供语言切换和记忆功能
  • 考虑 RTL(从右到左)语言布局
  • 日期、时间、数字格式本地化
  • 避免硬编码文本,全部使用翻译键

三、移动端开发

20. 移动端适配方案

常见适配方案对比

方案原理优点缺点适用场景
viewport 缩放设置 meta viewport简单直接精细控制难简单页面
rem + viewport根元素 font-size 动态计算兼容性好需要计算主流方案
vw/vh基于视口单位语义清晰兼容性一般现代项目
响应式布局媒体查询 + 弹性布局多端适配维护成本高响应式网站

方案一:rem + flexible

// flexible.js - 动态设置根元素 font-size
(function (doc, win) {
  const docEl = doc.documentElement;
  const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';

  function setFontSize() {
    const clientWidth = docEl.clientWidth;
    if (!clientWidth) return;
    // 设计稿宽度 750,基准 100px
    docEl.style.fontSize = (clientWidth / 750) * 100 + 'px';
  }

  if (!doc.addEventListener) return;
  win.addEventListener(resizeEvt, setFontSize, false);
  doc.addEventListener('DOMContentLoaded', setFontSize, false);
})(document, window);
// SCSS px 转 rem 混合器
$design-width: 750;
$base-font-size: 100;

@function px2rem($px) {
  @return ($px / $base-font-size) * 1rem;
}

.container {
  padding: px2rem(20);
  font-size: px2rem(16);
}

方案二:vw/vh(推荐)

/* 使用 postcss-px-to-viewport 自动转换 */
.container {
  width: 375px;    /* 转换为 50vw */
  height: 200px;   /* 转换为 26.67vw */
  padding: 20px;   /* 转换为 2.67vw */
}
// postcss.config.js
module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      viewportWidth: 375,     // 设计稿宽度
      viewportHeight: 667,    // 设计稿高度
      unitPrecision: 5,       // 小数位数
      viewportUnit: 'vw',     // 转换单位
      selectorBlackList: ['.ignore'], // 忽略的选择器
      minPixelValue: 1,       // 最小转换值
      mediaQuery: false,      // 媒体查询中是否转换
    },
  },
};

方案三:响应式设计

/* 媒体查询适配 */
.container {
  padding: 20px;
}

@media (max-width: 768px) {
  .container {
    padding: 15px;
  }
  .grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 480px) {
  .container {
    padding: 10px;
  }
  .title {
    font-size: 18px;
  }
}

适配注意事项

  • 1px 边框问题:使用 transform: scale(0.5) 或 viewport 缩放
  • 安全区域:适配 iPhone X 刘海屏(env(safe-area-inset-bottom))
  • 横竖屏切换:监听 orientationchange 事件
  • 字体大小:设置最小字体限制,避免过小

21. 移动端触摸事件处理与应用

触摸事件类型

事件说明
touchstart手指触摸屏幕
touchmove手指在屏幕上滑动
touchend手指离开屏幕
touchcancel触摸被中断(如来电)

手势识别实现

// 手势识别类
class GestureDetector {
  constructor(element, options = {}) {
    this.element = element;
    this.options = {
      tapThreshold: 10,     // 点击判定距离
      longPressDuration: 500, // 长按判定时间
      swipeThreshold: 30,    // 滑动判定距离
      ...options,
    };

    this.startX = 0;
    this.startY = 0;
    this.startTime = 0;
    this.longPressTimer = null;

    this.bindEvents();
  }

  bindEvents() {
    this.element.addEventListener('touchstart', this.handleStart.bind(this), { passive: false });
    this.element.addEventListener('touchmove', this.handleMove.bind(this), { passive: false });
    this.element.addEventListener('touchend', this.handleEnd.bind(this), { passive: false });
  }

  handleStart(e) {
    const touch = e.touches[0];
    this.startX = touch.clientX;
    this.startY = touch.clientY;
    this.startTime = Date.now();

    // 长按检测
    this.longPressTimer = setTimeout(() => {
      this.trigger('longpress', e);
    }, this.options.longPressDuration);
  }

  handleMove(e) {
    clearTimeout(this.longPressTimer);

    const touch = e.touches[0];
    const deltaX = touch.clientX - this.startX;
    const deltaY = touch.clientY - this.startY;

    // 阻止默认滚动行为
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
      e.preventDefault();
    }
  }

  handleEnd(e) {
    clearTimeout(this.longPressTimer);

    const touch = e.changedTouches[0];
    const deltaX = touch.clientX - this.startX;
    const deltaY = touch.clientY - this.startY;
    const deltaTime = Date.now() - this.startTime;

    // 点击检测
    if (Math.abs(deltaX) < this.options.tapThreshold &&
        Math.abs(deltaY) < this.options.tapThreshold &&
        deltaTime < 300) {
      this.trigger('tap', e);
    }

    // 滑动检测
    if (Math.abs(deltaX) > this.options.swipeThreshold) {
      this.trigger(deltaX > 0 ? 'swiperight' : 'swipeleft', e);
    }

    if (Math.abs(deltaY) > this.options.swipeThreshold) {
      this.trigger(deltaY > 0 ? 'swipedown' : 'swipeup', e);
    }
  }

  trigger(eventName, event) {
    this.element.dispatchEvent(new CustomEvent(eventName, { detail: event }));
  }
}

// 使用
const el = document.querySelector('.gesture-area');
const gesture = new GestureDetector(el);

el.addEventListener('tap', () => console.log('Tapped'));
el.addEventListener('swipeleft', () => console.log('Swiped Left'));
el.addEventListener('longpress', () => console.log('Long Pressed'));

300ms 点击延迟问题

<!-- 解决方案一:设置 viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- 解决方案二:CSS touch-action -->
<button style="touch-action: manipulation;">Click Me</button>

22. 移动端页面性能与用户体验优化

性能优化策略

1. 资源优化

  • 图片使用 WebP 格式,体积减少 30-50%
  • 按需加载,首屏资源优先
  • 使用懒加载和预加载策略
  • 减少第三方库依赖

2. 渲染优化

/* 使用 GPU 加速动画 */
.animated-element {
  transform: translate3d(0, 0, 0); /* 触发 GPU 合成层 */
  will-change: transform;
}

/* 避免布局抖动 */
img, video {
  aspect-ratio: attr(width) / attr(height);
}

3. 交互优化

  • 使用 CSS transition 代替 JavaScript 动画
  • 添加加载状态和骨架屏
  • 按钮点击添加视觉反馈(active 状态)
  • 预加载下一页数据
// 骨架屏示例
function SkeletonCard() {
  return (
    <div className="skeleton-card">
      <div className="skeleton-image" />
      <div className="skeleton-title" />
      <div className="skeleton-text" />
      <div className="skeleton-text short" />
    </div>
  );
}

// 使用
{
  isLoading ? <SkeletonCard /> : <ProductCard data={data} />;
}

4. 网络优化

  • 启用 HTTP/2
  • 使用 CDN 分发
  • 合理设置缓存策略
  • 接口合并和批量请求

5. 用户体验优化

  • 下拉刷新和上拉加载
  • 手势操作支持(滑动返回、下拉关闭)
  • 离线状态处理
  • 错误状态友好提示

23. Hybrid 开发与跨平台框架对比

技术对比

技术原理优点缺点代表框架
WebView 容器在原生中嵌入 WebView开发成本低性能一般Cordova
JS BridgeJS 与原生通信桥接可调用原生 API通信有开销各 Hybrid 方案
JS 渲染JS 调用原生组件渲染接近原生性能学习成本高React Native
自绘引擎自绘 UI,不依赖原生高度一致包体积大Flutter

React Native 核心原理

JS 线程(业务逻辑) ← Bridge → Native 线程(UI 渲染)
                                      ← Shadow Tree(布局计算)

Flutter 核心原理

Dart 代码 → Framework(Widget 树) → Engine(Skia 渲染引擎) → 直接绘制到屏幕

选型建议

  • React Native:已有 React 团队,需要调用原生 API
  • Flutter:追求一致的 UI 表现,高性能要求
  • 小程序:依托微信生态,轻量级应用
  • PWA:Web 优先,需要离线能力

四、桌面端开发

24. Electron 开发

定义

Electron 是使用 Web 技术(HTML/CSS/JS)构建跨平台桌面应用的框架,结合了 Chromium 渲染引擎和 Node.js 运行时。

架构组成

Electron = Chromium(渲染进程) + Node.js(主进程) + Native APIs

进程模型

  • 主进程(Main Process):控制应用生命周期,创建和管理渲染进程
  • 渲染进程(Renderer Process):展示 UI,每个窗口对应一个渲染进程
  • 进程通信(IPC):主进程与渲染进程之间通过 IPC 通信

基础示例

// main.js - 主进程
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

app.whenReady().then(() => {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  win.loadFile('index.html');
});

// IPC 通信
ipcMain.handle('read-file', async (event, filePath) => {
  const fs = require('fs').promises;
  return fs.readFile(filePath, 'utf-8');
});
// preload.js - 预加载脚本
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  getAppVersion: () => ipcRenderer.invoke('get-app-version'),
});
// renderer.js - 渲染进程
async function loadFile(path) {
  const content = await window.electronAPI.readFile(path);
  document.getElementById('content').textContent = content;
}

安全最佳实践

  • 启用 contextIsolationnodeIntegration: false
  • 使用 preload 脚本暴露安全的 API
  • 验证所有用户输入
  • 使用 CSP(内容安全策略)
  • 及时更新 Electron 版本

打包发布

// package.json
{
  "build": {
    "appId": "com.example.app",
    "productName": "My Desktop App",
    "directories": {
      "output": "release"
    },
    "mac": {
      "target": ["dmg", "zip"]
    },
    "win": {
      "target": ["nsis", "portable"]
    },
    "linux": {
      "target": ["AppImage", "deb"]
    }
  }
}
# 使用 electron-builder 打包
npx electron-builder --mac --win --linux

五、测试与调试

25. 前端测试体系

测试分层

层级工具测试内容占比
单元测试Jest / Vitest函数、工具类、单个组件70%
集成测试Testing Library组件间交互、模块协作20%
E2E 测试Cypress / Playwright完整用户流程10%

测试覆盖率指标

  • 行覆盖率(Line Coverage):有多少行代码被执行
  • 分支覆盖率(Branch Coverage):有多少分支路径被执行
  • 函数覆盖率(Function Coverage):有多少函数被调用
  • 语句覆盖率(Statement Coverage):有多少语句被执行

最佳实践

  • 优先测试业务逻辑和关键路径
  • 测试行为而非实现细节
  • 使用 Mock 隔离外部依赖
  • 保持测试独立性
  • 定期维护测试用例

26. 测试驱动开发(TDD)

定义

TDD(Test-Driven Development)是一种开发方法论,要求先编写失败的测试,再编写代码使测试通过,最后重构优化。

红-绿-重构循环

写一个失败的测试(红) → 写最少代码让测试通过(绿) → 重构代码优化设计 → 重复循环

示例

// 1. 先写测试
describe('sum', () => {
  test('should return the sum of two numbers', () => {
    expect(sum(2, 3)).toBe(5);
    expect(sum(-1, 1)).toBe(0);
    expect(sum(0, 0)).toBe(0);
  });
});

// 2. 编写代码使测试通过
function sum(a, b) {
  return a + b;
}

// 3. 重构(如有需要)
// 添加类型检查、错误处理等
function sum(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Arguments must be numbers');
  }
  return a + b;
}

六、其他技术

27. GraphQL

定义

GraphQL 是由 Facebook 开发的查询语言和运行时,允许客户端精确指定所需数据,替代传统的 REST API。

与 REST 的对比

特性RESTGraphQL
端点多个端点单一端点
数据获取固定格式按需查询
过度/不足获取常见问题不存在
版本管理URL 版本(/v1/, /v2/)字段废弃机制
实时数据WebSocket 额外实现Subscription 内置
缓存HTTP 缓存需客户端处理

GraphQL 查询示例

# 查询
query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}

# 变更
mutation CreatePost($input: PostInput!) {
  createPost(input: $input) {
    id
    title
    content
  }
}

# 订阅(实时)
subscription OnNewPost {
  newPost {
    id
    title
  }
}

Apollo Client 使用

import { ApolloClient, InMemoryCache, gql, useQuery } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache(),
});

// React 中使用
function UserProfile({ userId }) {
  const { loading, error, data } = useQuery(gql`
    query GetUser($id: ID!) {
      user(id: $id) {
        name
        email
      }
    }
  `, { variables: { id: userId } });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}

适用场景

  • 多端应用(Web、Mobile、Desktop)共享数据接口
  • 数据结构复杂,需要灵活查询
  • 需要减少网络请求次数
  • 实时数据需求(使用 Subscription)

28. WebSocket

定义

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。

特点

  • 全双工通信
  • 低延迟
  • 持久连接
  • 轻量级协议

实现示例

// 客户端
class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectInterval = 3000;
    this.maxReconnectAttempts = 5;
    this.reconnectAttempts = 0;
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectAttempts = 0;
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.handleMessage(data);
    };

    this.ws.onclose = () => {
      console.log('Disconnected, reconnecting...');
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  reconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      setTimeout(() => this.connect(), this.reconnectInterval);
    }
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  handleMessage(data) {
    console.log('Received:', data);
  }
}

// 使用
const wsClient = new WebSocketClient('wss://api.example.com/ws');
wsClient.connect();
wsClient.send({ type: 'subscribe', channel: 'updates' });

服务端实现(Node.js)

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  console.log('Client connected');

  ws.on('message', (message) => {
    const data = JSON.parse(message);
    console.log('Received:', data);

    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ type: 'broadcast', data }));
      }
    });
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

29. Server-Sent Events (SSE)

定义

SSE 是一种服务端向客户端推送单向事件的技术,基于 HTTP 协议,比 WebSocket 更简单。

特点

  • 单向通信(服务端到客户端)
  • 基于 HTTP,兼容性好
  • 内置自动重连
  • 文本格式(UTF-8)

实现示例

// 服务端(Express)
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 发送事件
  const sendEvent = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  // 定时推送
  const interval = setInterval(() => {
    sendEvent({ time: new Date().toISOString(), message: 'Hello!' });
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
  });
});

// 客户端
const eventSource = new EventSource('/events');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

eventSource.onerror = (error) => {
  console.error('SSE Error:', error);
  eventSource.close();
};

30. WebRTC

定义

WebRTC(Web Real-Time Communication)是一种支持浏览器进行实时音视频通信的技术。

核心 API

  • MediaStream:获取音视频流
  • RTCPeerConnection:建立点对点连接
  • RTCDataChannel:传输任意数据

基础示例

// 获取本地媒体流
async function getLocalStream() {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true,
  });
  return stream;
}

// 创建 PeerConnection
const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

// 发送本地流
stream.getTracks().forEach((track) => {
  peerConnection.addTrack(track, stream);
});

// 接收远程流
peerConnection.ontrack = (event) => {
  const remoteVideo = document.getElementById('remoteVideo');
  remoteVideo.srcObject = event.streams[0];
};

// 交换 ICE 候选
peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    // 通过信令服务器发送候选
    sendToPeer('ice-candidate', event.candidate);
  }
};

31. WebAssembly (Wasm)

定义

WebAssembly 是一种低级字节码格式,可在浏览器中以接近原生速度运行,支持 C、C++、Rust 等语言编译。

适用场景

  • 计算密集型任务(图像处理、视频编码)
  • 游戏引擎
  • 加密算法
  • 机器学习推理

使用示例

// 加载和运行 Wasm 模块
async function loadWasm() {
  const response = await fetch('module.wasm');
  const buffer = await response.arrayBuffer();

  const { instance } = await WebAssembly.instantiate(buffer, {
    env: {
      memory: new WebAssembly.Memory({ initial: 256 }),
    },
  });

  return instance.exports;
}

// 使用
const wasm = await loadWasm();
const result = wasm.calculate(42);
console.log(result);

32. PWA(Progressive Web App)

定义

PWA 是一种利用现代 Web API 构建具有原生应用体验的 Web 应用,核心特性包括:可安装、离线可用、可推送通知。

核心组成

  1. Service Worker:离线能力和后台同步
  2. Web App Manifest:安装配置
  3. HTTPS:安全传输

Manifest 配置

{
  "name": "My Progressive Web App",
  "short_name": "MyApp",
  "description": "A PWA example",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Service Worker 注册

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then((registration) => {
      console.log('SW registered:', registration);
    }).catch((error) => {
      console.log('SW registration failed:', error);
    });
  });
}

33. Service Worker

定义

Service Worker 是在后台运行的脚本,独立于网页,可拦截网络请求、缓存资源、处理推送通知。

生命周期

注册 → 安装 → 激活 → 监听请求 → 闲置/终止

完整示例

// sw.js
const CACHE_NAME = 'my-app-v1';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icon.png',
];

// 安装阶段:缓存静态资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(ASSETS_TO_CACHE);
    })
  );
  self.skipWaiting();
});

// 激活阶段:清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      );
    })
  );
});

// 请求拦截:缓存优先,网络回退
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      if (cachedResponse) return cachedResponse;

      return fetch(event.request).then((response) => {
        if (response.ok) {
          const responseClone = response.clone();
          caches.open(CACHE_NAME).then((cache) => {
            cache.put(event.request, responseClone);
          });
        }
        return response;
      });
    }).catch(() => {
      return caches.match('/offline.html');
    })
  );
});

缓存策略

策略说明适用场景
Cache First优先使用缓存静态资源
Network First优先使用网络频繁更新的数据
Stale While Revalidate缓存+后台更新非关键数据
Network Only仅使用网络实时数据
Cache Only仅使用缓存离线页面

34. IndexedDB

定义

IndexedDB 是浏览器提供的客户端存储方案,支持大量结构化数据的存储和查询。

特点

  • 存储容量大(通常 250MB+)
  • 支持事务和索引
  • 异步 API
  • 键值对存储

封装示例

class DB {
  constructor(name, version, stores) {
    this.db = null;
    this.name = name;
    this.version = version;
    this.stores = stores;
  }

  open() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.name, this.version);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        this.stores.forEach((store) => {
          if (!db.objectStoreNames.contains(store.name)) {
            db.createObjectStore(store.name, { keyPath: store.keyPath });
          }
        });
      };

      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(this.db);
      };

      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }

  add(storeName, data) {
    return new Promise((resolve, reject) => {
      const tx = this.db.transaction(storeName, 'readwrite');
      const store = tx.objectStore(storeName);
      const request = store.add(data);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  get(storeName, key) {
    return new Promise((resolve, reject) => {
      const tx = this.db.transaction(storeName, 'readonly');
      const store = tx.objectStore(storeName);
      const request = store.get(key);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
}

// 使用
const db = new DB('my-app', 1, [
  { name: 'users', keyPath: 'id' },
  { name: 'posts', keyPath: 'id' },
]);

await db.open();
await db.add('users', { id: 1, name: 'John' });
const user = await db.get('users', 1);

35. Web Workers

定义

Web Workers 允许在后台线程中运行 JavaScript,不阻塞主线程(UI 线程)。

使用场景

  • 大量数据处理(排序、过滤)
  • 图片/视频处理
  • 加密计算
  • 复杂算法(路径规划、物理引擎)
  • WebSocket 通信

实现示例

// worker.js
self.addEventListener('message', (event) => {
  const { data, type } = event.data;

  switch (type) {
    case 'process-large-data':
      const result = processData(data);
      self.postMessage({ type: 'result', data: result });
      break;

    case 'calculate':
      const computed = heavyCalculation(data);
      self.postMessage({ type: 'calculation-done', data: computed });
      break;
  }
});

function processData(data) {
  // 模拟大量数据处理
  return data.map((item) => ({
    ...item,
    processed: true,
    timestamp: Date.now(),
  }));
}

function heavyCalculation(input) {
  let result = 0;
  for (let i = 0; i < input * 1000000; i++) {
    result += Math.random();
  }
  return result;
}
// main.js
const worker = new Worker('./worker.js');

// 发送数据到 Worker
worker.postMessage({
  type: 'process-large-data',
  data: largeArray,
});

// 接收 Worker 结果
worker.onmessage = (event) => {
  const { type, data } = event.data;

  switch (type) {
    case 'result':
      console.log('Processed:', data);
      break;
    case 'calculation-done':
      console.log('Calculated:', data);
      break;
  }
};

// 错误处理
worker.onerror = (error) => {
  console.error('Worker error:', error);
};

// 终止 Worker
worker.terminate();

最佳实践

  • 不要传递大量数据(使用 Transferable Objects)
  • 控制 Worker 数量(通常不超过 CPU 核心数)
  • 复用 Worker 实例,避免频繁创建销毁
  • 处理 Worker 错误,防止崩溃

36. Web Components

定义

Web Components 是一套浏览器原生支持的组件化技术,允许创建可复用的自定义元素。

三大核心技术

  1. Custom Elements:定义自定义元素
  2. Shadow DOM:封装样式和 DOM
  3. HTML Templates:定义可复用的模板

Custom Elements

// 定义自定义元素
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
      <style>
        button {
          padding: 10px 20px;
          border: none;
          border-radius: 4px;
          background: #007bff;
          color: white;
          cursor: pointer;
        }
        button:hover {
          background: #0056b3;
        }
      </style>
      <button><slot>Click Me</slot></button>
    `;

    this.shadowRoot.querySelector('button').addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('my-button-click', {
        bubbles: true,
        composed: true,
      }));
    });
  }

  connectedCallback() {
    console.log('MyButton added to page');
  }

  disconnectedCallback() {
    console.log('MyButton removed from page');
  }
}

customElements.define('my-button', MyButton);

Shadow DOM

// Shadow DOM 提供样式和 DOM 隔离
const shadow = element.attachShadow({ mode: 'open' });

// mode: 'open'  - 可从外部访问 shadowRoot
// mode: 'closed' - 不可从外部访问

HTML Templates

<template id="card-template">
  <style>
    .card {
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 16px;
    }
  </style>
  <div class="card">
    <h2 class="title"></h2>
    <p class="content"></p>
  </div>
</template>

<script>
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.title').textContent = 'Card Title';
clone.querySelector('.content').textContent = 'Card content';
document.body.appendChild(clone);
</script>

七、项目经验

37. 项目介绍

回答框架:STAR 法则

  • S(Situation):项目背景和业务场景
  • T(Task):你的职责和任务
  • A(Action):你采取的具体行动和技术方案
  • R(Result):项目成果和量化指标

回答模板

我参与的第一个项目是 [项目名称],这是一个 [项目类型/业务场景]。
项目的主要目标是 [核心目标],我主要负责 [你的职责]。

在技术选型方面,我们使用了 [技术栈],选择原因是 [选型理由]。

项目过程中我遇到的最大挑战是 [难点],我通过 [解决方案] 解决了这个问题。

最终项目取得了 [量化成果],比如 [具体数据]

38. 项目难点

常见难点及回答方向

1. 性能优化类

  • 问题:首屏加载慢、列表卡顿
  • 解决:代码分割、虚拟列表、图片优化、缓存策略
  • 成果:首屏时间从 Xs 降低到 Ys

2. 复杂业务逻辑

  • 问题:表单联动、状态管理复杂
  • 解决:状态机、表单引擎、组件抽象
  • 成果:可维护性提升,开发效率提高

3. 跨端适配

  • 问题:多端兼容、响应式适配
  • 解决:响应式设计、组件封装、差异化处理
  • 成果:一套代码多端运行

4. 团队协作

  • 问题:多人协作冲突、代码质量参差不齐
  • 解决:代码规范、自动化检查、Code Review
  • 成果:代码质量提升,Bug 率下降

39. 项目亮点

亮点展示方向

  • 技术创新:引入了某项新技术或方案
  • 性能提升:显著的量化优化成果
  • 工程化建设:脚手架、组件库、工具链
  • 业务价值:提升了用户体验或转化率
  • 可复用性:抽象出通用方案,赋能其他项目

40. 项目架构设计

架构设计要点

前端架构设计维度
├── 技术选型(框架、工具链)
├── 目录结构(模块化组织)
├── 状态管理(全局状态方案)
├── 路由设计(权限、懒加载)
├── 组件设计(基础组件、业务组件)
├── 接口层(API 封装、拦截器)
├── 权限管理(路由权限、按钮权限)
└── 监控体系(错误、性能、埋点)

典型目录结构

src/
├── api/              # 接口层
├── assets/           # 静态资源
├── components/       # 通用组件
├── hooks/            # 自定义 Hooks
├── layouts/          # 布局组件
├── pages/            # 页面组件
├── router/           # 路由配置
├── store/            # 状态管理
├── styles/           # 全局样式
├── utils/            # 工具函数
└── main.js           # 入口文件

41. 技术选型

技术选型原则

  1. 业务匹配:技术方案是否适合业务需求
  2. 团队能力:团队是否具备相关技术能力
  3. 生态成熟度:社区活跃度、文档完善程度
  4. 可维护性:代码可读性、可扩展性
  5. 性能要求:是否满足性能指标
  6. 长期成本:学习成本、维护成本

常见技术选型对比

维度ReactVueAngular
学习曲线中等
生态最丰富丰富完善
灵活性
TypeScript支持好支持好原生支持
适用场景中大型项目中小型项目企业级应用

42. 团队协作与代码管理

Git 协作流程

功能分支工作流(Git Flow)

main(生产)
└── develop(开发)
    ├── feature/user-auth(新功能)
    ├── feature/payment(新功能)
    ├── bugfix/login-error(Bug 修复)
    └── release/v1.2.0(发布准备)

Commit 规范(Conventional Commits)

feat: 新增用户登录功能
fix: 修复首页数据加载异常
docs: 更新 API 文档
style: 统一按钮样式
refactor: 重构订单状态管理
test: 增加用户注册测试
chore: 升级依赖包版本

Code Review 流程

  1. 开发者提交 PR/MR
  2. 自动化检查(CI 流水线)
  3. 团队成员评审
  4. 根据反馈修改
  5. 审核通过后合并

八、软技能

43. 沟通能力

沟通要点

  • 倾听理解:先理解需求,再提出方案
  • 清晰表达:用对方能理解的语言描述技术问题
  • 及时反馈:遇到问题主动沟通,不闷头干活
  • 文档沉淀:重要决策和方案形成文档

跨部门沟通示例

产品:这个功能很急,能不能今天上线?
前端:理解这个功能的紧急性,但目前还有 X 个依赖需要联调,
      预计需要 Y 小时完成测试。我建议分阶段发布,
      先上线核心功能,其余功能明天发布,您看可以吗?

44. 团队协作

协作要点

  • 主动分享:定期技术分享,提升团队整体水平
  • 互相支持:遇到卡点及时求助,也主动帮助他人
  • 代码规范:遵守团队约定,保持代码一致性
  • 知识沉淀:文档化技术方案和踩坑经验

45. 问题解决能力

问题解决步骤

问题描述 → 信息收集 → 原因分析 → 方案制定 → 方案执行 → 验证结果 → 经验总结

排查工具

  • Chrome DevTools:Elements、Network、Performance、Console
  • Source Map:定位压缩后代码的原始位置
  • 日志系统:前端错误监控平台
  • 网络抓包:Charles、Wireshark

46. 学习能力

高效学习方法

  • 费曼技巧:通过教别人来检验自己的理解
  • 实践驱动:边学边做,项目驱动学习
  • 系统性学习:先建立知识框架,再深入细节
  • 持续输出:写博客、做分享,加深理解

九、职业发展

47. 职业规划

回答框架

短期(1-2年):深耕前端技术,成为团队核心开发者
               - 深入理解框架原理
               - 参与工程化建设
               - 提升架构设计能力

中期(3-5年):技术专家/技术管理方向
               - 主导技术选型和架构设计
               - 培养和指导团队成员
               - 推动技术创新

长期(5年以上):技术负责人/架构师
               - 技术战略规划
               - 团队管理与技术决策
               - 行业影响力建设

48. 技术成长路径

前端工程师成长阶段

阶段能力要求时间
初级能独立完成分配的任务0-2年
中级能独立负责模块,解决复杂问题2-4年
高级能主导项目架构,指导他人4-6年
专家技术战略规划,推动技术创新6年以上

49. 前端技术趋势

当前趋势

  • AI 辅助开发:Copilot 等 AI 编程助手
  • 全栈化:Next.js/Nuxt.js 推动前后端融合
  • 微前端:大型应用的模块化方案
  • 低代码/无代码:提效工具
  • 跨端统一:React Native、Flutter、Taro
  • WebAssembly:前端性能边界拓展

学习方向建议

  • 深入一个框架(React 或 Vue)
  • 掌握服务端渲染(SSR)
  • 了解 Node.js 和全栈开发
  • 关注 Web 新 API 和标准
  • 学习工程化和架构设计

50. 学习方法与资源

学习资源

  • 官方文档:最权威的学习资料
  • 开源项目:学习最佳实践
  • 技术博客:了解实战经验
  • 技术社区:Stack Overflow、GitHub、掘金
  • 在线课程:系统性学习

知识管理

  • 建立个人知识库(Notion、Obsidian)
  • 定期整理和输出
  • 参与开源项目
  • 写技术博客

十、综合能力

51. 系统设计能力

系统设计框架

需求分析 → 功能拆解 → 技术选型 → 架构设计 → 接口设计 → 数据流设计 → 异常处理 → 性能优化 → 监控方案

前端监控体系设计

监控体系
├── 错误监控
│   ├── JS 错误(window.onerror)
│   ├── Promise 错误(unhandledrejection)
│   ├── 资源加载错误
│   └── 接口异常
├── 性能监控
│   ├── Web VitalsLCPFIDCLS)
│   ├── 页面加载时间
│   └── 接口响应时间
└── 行为监控
    ├── 页面访问统计
    ├── 用户操作记录
    └── 转化漏斗分析

52. 前端监控实现

错误收集

class ErrorMonitor {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/error-report';
    this.appName = options.appName || 'unknown';
  }

  init() {
    this.captureJSError();
    this.capturePromiseError();
    this.captureResourceError();
    this.captureVueError();
    this.captureReactError();
  }

  captureJSError() {
    window.onerror = (message, source, lineno, colno, error) => {
      this.report({
        type: 'js-error',
        message,
        source,
        lineno,
        colno,
        stack: error?.stack,
        url: window.location.href,
        userAgent: navigator.userAgent,
      });
    };
  }

  capturePromiseError() {
    window.addEventListener('unhandledrejection', (event) => {
      this.report({
        type: 'promise-error',
        message: event.reason?.message || String(event.reason),
        stack: event.reason?.stack,
        url: window.location.href,
      });
    });
  }

  captureResourceError() {
    window.addEventListener('error', (event) => {
      if (event.target instanceof HTMLElement) {
        this.report({
          type: 'resource-error',
          tagName: event.target.tagName,
          src: event.target.src || event.target.href,
          url: window.location.href,
        });
      }
    }, true);
  }

  report(data) {
    const reportData = {
      ...data,
      appName: this.appName,
      timestamp: Date.now(),
    };

    // 使用 Beacon API 确保数据发送成功
    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.apiUrl, JSON.stringify(reportData));
    } else {
      fetch(this.apiUrl, {
        method: 'POST',
        body: JSON.stringify(reportData),
        keepalive: true,
      });
    }
  }
}

// 使用
const monitor = new ErrorMonitor({
  apiUrl: '/api/error-report',
  appName: 'my-app',
});
monitor.init();

十一、跨域解决方案

53. 跨域问题及解决方案

同源策略

浏览器出于安全考虑,限制不同源(协议 + 域名 + 端口)之间的资源访问。

解决方案对比

方案原理适用场景优缺点
CORS服务端设置响应头主流方案简单、标准
JSONP动态创建 script 标签旧浏览器仅支持 GET
代理服务器同源服务器转发请求开发/生产万能方案
WebSocket不受同源策略限制实时通信特定场景
postMessage跨窗口通信iframe特定场景

CORS 配置

// Express 服务端
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  if (req.method === 'OPTIONS') {
    res.sendStatus(204);
    return;
  }
  next();
});

Nginx 代理

server {
  listen 80;
  server_name localhost;

  location /api/ {
    proxy_pass https://api.example.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

十二、常见设计模式

54. 前端常用设计模式

1. 观察者模式

class EventEmitter {
  constructor() {
    this.events = new Map();
  }

  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(callback);
  }

  emit(event, ...args) {
    const callbacks = this.events.get(event);
    if (callbacks) {
      callbacks.forEach((cb) => cb.apply(this, args));
    }
  }

  off(event, callback) {
    const callbacks = this.events.get(event);
    if (callbacks) {
      this.events.set(
        event,
        callbacks.filter((cb) => cb !== callback)
      );
    }
  }
}

2. 发布订阅模式

// Vue 的双向绑定核心
class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  notify() {
    this.subs.forEach((sub) => sub.update());
  }
}

class Watcher {
  constructor(update) {
    this.update = update;
  }
}

3. 单例模式

// Vuex / Redux Store
class Store {
  static instance = null;

  static getInstance() {
    if (!Store.instance) {
      Store.instance = new Store();
    }
    return Store.instance;
  }
}

4. 策略模式

// 表单验证策略
const validators = {
  required: (value) => value !== '',
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  min: (value, min) => value.length >= min,
};

function validate(value, rules) {
  for (const [rule, params] of Object.entries(rules)) {
    if (!validators[rule](value, params)) {
      return false;
    }
  }
  return true;
}

5. 代理模式

// Vue 3 响应式原理
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key);
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      Reflect.set(target, key, value);
      trigger(target, key);
      return true;
    },
  });
}

十三、面试技巧

55. 自我介绍

回答模板

您好,我是 [姓名],有 [X] 年前端开发经验。

技术方面,我熟练掌握 [技术栈],擅长 [核心能力]。
最近一年主要负责 [项目类型/业务],取得了 [成果]。

在项目经验方面,我参与过 [项目名称],负责 [具体工作]。
其中 [亮点项目],我通过 [技术手段],实现了 [效果]。

我对这个岗位非常感兴趣,因为 [匹配点]。
希望有机会加入贵公司,谢谢!

56. 薪资谈判

谈判策略

  • 提前调研:了解市场行情和公司薪资水平
  • 合理期望:基于自身能力和经验给出范围
  • 综合考量:薪资、福利、发展空间、工作环境
  • 不急不躁:给出考虑时间,不要当场决定

话术参考

基于我的经验和能力,以及市场行情,我的期望薪资是 [范围]。
当然,我也看重公司的发展平台和团队氛围,
薪资只是考虑因素之一,我愿意综合评估整个 offer。

57. Offer 选择

考虑维度

维度权重评估点
薪资福利25%基本工资、奖金、五险一金、补贴
发展空间25%晋升通道、技术成长、学习机会
团队氛围20%同事水平、管理方式、文化匹配
公司前景15%行业地位、业务发展、融资情况
工作强度15%加班情况、弹性工作、远程办公

十四、代码规范

58. 前端代码规范

JavaScript/TypeScript 规范

// 命名规范
const MAX_RETRY_COUNT = 3;        // 常量:大写蛇形
let userName = 'John';            // 变量:驼峰
function calculateTotal() {}      // 函数:驼峰
class UserProfile {}              // 类:大驼峰
interface UserData {}             // 接口:大驼峰,I 前缀可选

// 文件规范
// 每个文件只做一件事
// 导出接口优先于导出实现
// 使用命名导出而非默认导出(便于重构)

CSS 规范

// BEM 命名规范
.block {}
.block__element {}
.block--modifier {}

// 属性书写顺序
.element {
  // 1. 定位
  position: absolute;
  top: 0;
  left: 0;
  z-index: 10;

  // 2. 盒模型
  display: flex;
  width: 100%;
  padding: 16px;
  margin: 0;

  // 3. 排版
  font-size: 16px;
  color: #333;

  // 4. 视觉
  background: #fff;
  border: 1px solid #ddd;

  // 5. 其他
  transition: all 0.3s;
}

十五、需求分析与部署发布

59. 需求分析与功能设计

需求分析流程

需求收集 → 需求评审 → 技术方案设计 → 任务拆解 → 排期 → 开发 → 测试 → 上线

技术方案设计文档结构

  • 需求概述
  • 技术方案
  • 接口设计
  • 数据流设计
  • 组件设计
  • 风险点和备选方案
  • 排期计划

60. 部署发布

发布流程

代码合并 → CI 构建 → 自动化测试 → 预发布环境验证 → 生产发布 → 监控观察 → 回滚准备

发布检查清单

  • 代码已合并并通过 CI
  • 自动化测试全部通过
  • 预发布环境验证通过
  • 数据库变更脚本准备
  • 回滚方案准备
  • 相关人员已通知
  • 监控已配置告警