其他
一、前端工程化
1. 什么是前端工程化?如何实施前端工程化?
定义
前端工程化是指将软件工程的理念、方法和工具应用到前端开发中,通过标准化、自动化、模块化的手段,提升前端项目的开发效率、代码质量、可维护性和团队协作能力。
核心理念
- 一切皆模块:将 CSS、JS、图片、字体等资源都视为模块进行管理
- 一切皆可自动化:构建、测试、部署等流程尽可能自动化
- 标准化规范:统一的代码规范、提交规范、分支管理规范
- 可度量指标:代码质量、构建速度、测试覆盖率等指标可量化
实施步骤
- 技术选型:选择适合的构建工具(Vite/Webpack/Rollup)、包管理器、代码规范工具
- 规范制定:ESLint、Prettier、StyleLint、CommitLint
- 脚手架搭建:创建项目模板,统一项目结构
- 构建流程:配置打包、压缩、代码分割、Tree Shaking
- 自动化测试:单元测试、集成测试、E2E 测试
- CI/CD 流程:自动化构建、测试、部署
- 监控体系:错误监控、性能监控、埋点统计
核心工具链
| 类别 | 工具 | 作用 |
|---|---|---|
| 构建工具 | Vite/Webpack | 模块打包、代码转换 |
| 代码规范 | ESLint | 代码质量检查 |
| 代码格式化 | Prettier | 统一代码风格 |
| 提交规范 | Husky + CommitLint | Git 提交信息规范 |
| 包管理 | npm/yarn/pnpm | 依赖管理 |
| 测试框架 | Jest/Vitest | 单元测试 |
| E2E 测试 | Cypress/Playwright | 端到端测试 |
| CI/CD | GitHub Actions/Jenkins | 持续集成/部署 |
常见误区
- 工程化不只是使用 Webpack 或 Vite,而是一个完整的体系
- 不是工具越多越好,要根据团队和项目规模选择合适的方案
- 工程化不是一次性工作,需要持续优化和维护
2. 前端工程化的目标和实施步骤
目标
- 提高开发效率:通过脚手架、代码生成、热更新等手段减少重复工作
- 提升代码质量:通过代码规范、静态检查、自动化测试保证代码质量
- 优化构建性能:缩短构建时间,提升开发体验
- 标准化流程:统一团队开发流程,降低协作成本
- 可维护性:模块化、组件化设计,便于后期维护和扩展
实施步骤
需求分析 → 技术选型 → 规范制定 → 脚手架搭建 → 构建配置 → 测试体系 → 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)
定义
脚手架是用于快速创建项目基础结构的工具,通过模板化和自动化减少项目初始化时的重复工作。
核心功能
- 项目模板生成:根据预设模板生成项目目录结构
- 配置文件生成:生成 ESLint、Prettier、TypeScript、构建工具等配置
- 依赖安装:自动安装项目所需的依赖包
- 初始化脚本:运行初始化脚本完成额外配置
实现方式
- 使用现有工具: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) → 代码检查 → 模块打包 → 代码压缩 → 资源优化 → 产物输出
构建工具对比
| 特性 | Webpack | Vite | Rollup |
|---|---|---|---|
| 开发服务器 | webpack-dev-server | 基于 esbuild | 不内置 |
| 构建速度 | 较慢 | 极快(esbuild 预构建) | 较快 |
| 模块格式 | 多种 | ESM | ESM 为主 |
| 适用场景 | 大型复杂项目 | 中小型项目 | 库/框架打包 |
| 配置复杂度 | 高 | 低 | 中 |
| 生态丰富度 | 最丰富 | 快速增长中 | 一般 |
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. 代码质量与代码审查
代码质量保障体系
- 静态分析:ESLint、StyleLint、TypeScript 类型检查
- 代码格式化:Prettier 统一代码风格
- 提交前检查:Husky + lint-staged
- 代码审查:Pull Request 流程
- 质量门禁: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 核心指标
| 指标 | 全称 | 含义 | 优秀标准 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容绘制时间 | ≤ 2.5s |
| FID | First Input Delay | 首次输入延迟 | ≤ 100ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | ≤ 0.1 |
| INP | Interaction to Next Paint | 交互到下次绘制 | ≤ 200ms |
| TTFB | Time to First Byte | 首字节时间 | ≤ 800ms |
| FCP | First Contentful Paint | 首次内容绘制 | ≤ 1.8s |
指标测量工具
- Lighthouse:Chrome DevTools 内置,综合评分
- WebPageTest:详细的性能分析报告
- Chrome DevTools Performance:运行时性能分析
- Performance API:
window.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,避免阻塞渲染
- 使用
defer或async加载脚本
<!-- 推荐的脚本加载方式 -->
<script defer src="/app.js"></script> <!-- DOM 解析完成后执行 -->
<script async src="/analytics.js"></script> <!-- 异步加载,不阻塞 -->
CSS 优化
- 关键 CSS 内联,非关键 CSS 异步加载
- 避免使用高成本的 CSS 属性(如
box-shadow、filter) - 使用 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. 图片懒加载
定义
图片懒加载是指图片在进入可视区域之前不加载,进入可视区域后才开始加载的技术,可以显著减少首屏加载时间和带宽消耗。
实现原理
- 图片初始
src设置为占位图或为空,真实地址存储在data-src属性中 - 监听滚动事件或使用 IntersectionObserver 检测图片是否进入视口
- 图片进入视口后,将
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 Bridge | JS 与原生通信桥接 | 可调用原生 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;
}
安全最佳实践
- 启用
contextIsolation和nodeIntegration: 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 的对比
| 特性 | REST | GraphQL |
|---|---|---|
| 端点 | 多个端点 | 单一端点 |
| 数据获取 | 固定格式 | 按需查询 |
| 过度/不足获取 | 常见问题 | 不存在 |
| 版本管理 | 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 应用,核心特性包括:可安装、离线可用、可推送通知。
核心组成
- Service Worker:离线能力和后台同步
- Web App Manifest:安装配置
- 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 是一套浏览器原生支持的组件化技术,允许创建可复用的自定义元素。
三大核心技术
- Custom Elements:定义自定义元素
- Shadow DOM:封装样式和 DOM
- 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. 技术选型
技术选型原则
- 业务匹配:技术方案是否适合业务需求
- 团队能力:团队是否具备相关技术能力
- 生态成熟度:社区活跃度、文档完善程度
- 可维护性:代码可读性、可扩展性
- 性能要求:是否满足性能指标
- 长期成本:学习成本、维护成本
常见技术选型对比
| 维度 | React | Vue | Angular |
|---|---|---|---|
| 学习曲线 | 中等 | 低 | 高 |
| 生态 | 最丰富 | 丰富 | 完善 |
| 灵活性 | 高 | 中 | 低 |
| 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 流程
- 开发者提交 PR/MR
- 自动化检查(CI 流水线)
- 团队成员评审
- 根据反馈修改
- 审核通过后合并
八、软技能
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 Vitals(LCP、FID、CLS)
│ ├── 页面加载时间
│ └── 接口响应时间
└── 行为监控
├── 页面访问统计
├── 用户操作记录
└── 转化漏斗分析
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
- 自动化测试全部通过
- 预发布环境验证通过
- 数据库变更脚本准备
- 回滚方案准备
- 相关人员已通知
- 监控已配置告警