设计模式的一些笔记

单例模式(Singleton)

核心思想:确保一个类 / 对象只有一个实例,并提供全局访问点。
适用场景:全局状态管理(如 Redux 的store)、全局模态框、全局缓存等。

class SingletonModal {
  constructor() {
    // 若已存在实例,直接返回
    if (SingletonModal.instance) return SingletonModal.instance;
    // 初始化唯一实例
    this.element = document.createElement('div');
    this.element.className = 'modal';
    document.body.appendChild(this.element);
    // 缓存实例
    SingletonModal.instance = this;
  }

  show(content) {
    this.element.textContent = content;
    this.element.style.display = 'block';
  }

  hide() {
    this.element.style.display = 'none';
  }
}

// 测试:多次创建仍为同一实例
const modal1 = new SingletonModal();
const modal2 = new SingletonModal();
console.log(modal1 === modal2); // true(同一实例)

观察者模式(Observer)

核心思想:定义对象间的一对多依赖关系,当一个对象状态变化时,所有依赖它的对象自动收到通知。
适用场景:事件监听、状态管理(如 Vue 响应式、React 的useEffect)、发布 - 订阅系统。

class EventEmitter {
  constructor() {
    this.events = {}; // 存储事件订阅:{ 'eventName': [callback1, callback2] }
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) this.events[eventName] = [];
    this.events[eventName].push(callback);
  }

  // 发布事件(通知所有订阅者)
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => callback(...args));
    }
  }

  // 取消订阅
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
    }
  }
}

// 测试
const emitter = new EventEmitter();
const log = (msg) => console.log('收到消息:', msg);

emitter.on('message', log);
emitter.emit('message', 'Hello 观察者模式'); // 输出:收到消息:Hello 观察者模式
emitter.off('message', log);
emitter.emit('message', '再发一次'); // 无输出(已取消订阅)

工厂模式(Factory)

核心思想:封装对象的创建逻辑,通过 “工厂” 统一创建不同类型的对象,隐藏创建细节。
适用场景:组件库(根据类型创建不同组件)、表单元素生成、多类型数据解析。

// 定义不同类型的组件
class Button { render() { return '<button>按钮</button>'; } }
class Input { render() { return '<input type="text">'; } }
class Select { render() { return '<select></select>'; } }

// 组件工厂:根据类型创建对应组件
class ComponentFactory {
  static createComponent(type) {
    switch (type) {
      case 'button': return new Button();
      case 'input': return new Input();
      case 'select': return new Select();
      default: throw new Error('未知组件类型');
    }
  }
}

// 测试:通过工厂创建组件
const button = ComponentFactory.createComponent('button');
console.log(button.render()); // <button>按钮</button>

装饰器模式(Decorator)

核心思想:动态给对象添加额外功能,不改变其原有结构,实现 “功能扩展” 而非 “继承”。
适用场景:React 高阶组件(HOC)、日志增强、权限控制、性能埋点。

// 基础功能:普通按钮
class Button {
  click() {
    console.log('按钮被点击');
  }
}

// 装饰器1:添加日志功能
function withLog(Component) {
  return class extends Component {
    click() {
      console.log('【日志】记录点击时间:', new Date()); // 新增功能
      super.click(); // 保留原功能
    }
  };
}

// 装饰器2:添加权限校验
function withAuth(Component) {
  return class extends Component {
    click() {
      const hasPermission = true; // 实际项目中从权限系统获取
      if (hasPermission) {
        super.click(); // 有权限才执行原功能
      } else {
        console.log('【权限】无点击权限');
      }
    }
  };
}

// 应用装饰器
const EnhancedButton = withAuth(withLog(Button));
new EnhancedButton().click();
// 输出:
// 【日志】记录点击时间:XXX
// 按钮被点击

代理模式(Proxy)

核心思想:通过一个 “代理对象” 控制对原对象的访问,可在访问前后添加额外逻辑(如拦截、缓存、验证)。
适用场景:Vue3 响应式(Proxy实现数据劫持)、图片懒加载、请求拦截(Axios 拦截器)。

// 原对象:图片加载
class ImageLoader {
  load(url) {
    const img = new Image();
    img.src = url;
    console.log('加载图片:', url);
  }
}

// 代理对象:实现图片懒加载(滚动到可视区才加载)
class LazyLoadProxy {
  constructor(loader) {
    this.loader = loader;
    this.urls = []; // 缓存未加载的图片
    this.bindScroll(); // 监听滚动事件
  }

  // 代理load方法:先缓存,滚动时判断是否加载
  load(url) {
    this.urls.push(url);
    this.checkLoad(); // 立即检查一次
  }

  // 检查是否需要加载(简化版:假设可视区高度为500px)
  checkLoad() {
    const scrollTop = document.documentElement.scrollTop;
    this.urls = this.urls.filter(url => {
      if (scrollTop < 500) { // 模拟:滚动到可视区
        this.loader.load(url);
        return false; // 已加载,从缓存移除
      }
      return true; // 未加载,保留缓存
    });
  }

  bindScroll() {
    window.addEventListener('scroll', () => this.checkLoad());
  }
}

// 测试:通过代理实现懒加载
const loader = new ImageLoader();
const proxy = new LazyLoadProxy(loader);
proxy.load('image1.jpg'); // 初始不加载(假设滚动条在顶部)
proxy.load('image2.jpg');
// 当滚动时,符合条件的图片会被加载

模块模式(Module)

核心思想:通过闭包创建私有变量和方法,只暴露必要的接口,实现 “信息隐藏”。
适用场景:ES6 模块出现前的模块化开发(如 jQuery 源码)、工具类封装。

const ToolModule = (function() {
  // 私有变量(外部无法直接访问)
  const privateKey = 'secret';

  // 私有方法
  function privateEncrypt(str) {
    return str + privateKey;
  }

  // 暴露公共接口
  return {
    encrypt: (str) => privateEncrypt(str),
    decrypt: (str) => str.replace(privateKey, '')
  };
})();

// 测试:只能访问暴露的方法
console.log(ToolModule.encrypt('test')); // testsecret
console.log(ToolModule.decrypt('testsecret')); // test
console.log(ToolModule.privateKey); // undefined(私有变量不可访问)

策略模式(Strategy)

核心思想:定义一系列算法(策略),将其封装为独立对象,可动态切换使用。
适用场景:表单验证(不同字段用不同验证规则)、支付方式选择、排序算法切换。

// 定义验证策略(不同算法)
const validators = {
  required: (value) => value.trim() !== '' || '必填项',
  email: (value) => /^[\w-]+@([\w-]+\.)+[\w-]+$/.test(value) || '邮箱格式错误',
  minLength: (min) => (value) => value.length >= min || `至少${min}个字符`
};

// 验证器:根据策略执行验证
class Validator {
  constructor() {
    this.rules = []; // 存储字段的验证规则
  }

  // 添加验证规则(策略)
  addRule(field, strategy, ...args) {
    const ruleFunc = typeof strategy === 'function' 
      ? strategy(...args) 
      : validators[strategy];
    this.rules.push({ field, ruleFunc });
  }

  // 执行验证
  validate(data) {
    for (const { field, ruleFunc } of this.rules) {
      const error = ruleFunc(data[field]);
      if (error !== true) return { field, error }; // 返回第一个错误
    }
    return null; // 无错误
  }
}

// 测试:验证表单
const validator = new Validator();
validator.addRule('username', 'required');
validator.addRule('email', 'email');
validator.addRule('password', 'minLength', 6);

const formData = { username: '', email: 'invalid', password: '123' };
console.log(validator.validate(formData)); 
// 输出:{ field: 'username', error: '必填项' }(第一个错误)

前端框架中的设计模式
React:使用观察者模式(状态更新触发重新渲染)、装饰器模式(HOC)、组合模式(组件嵌套)。
Vue:使用代理模式(Proxy实现响应式)、观察者模式(watch/computed)、工厂模式(Vue.component)。
Redux:使用单例模式(store唯一)、观察者模式(subscribe订阅状态变化)。

选择设计模式的核心原则:“解决问题” 而非 “为了用而用”。简单场景无需过度设计,复杂场景(如组件库、状态管理)合理应用模式可显著提升代码质量。

日常开发中的使用

虽然前端框架(如 React、Vue)确实内置了很多设计模式(比如 React 的组件树用了组合模式,Vue 的响应式用了代理模式),但实际业务场景中,我们仍需要主动运用设计模式解决具体问题。

1. 框架未覆盖的业务场景

复杂表单验证:框架不会帮你实现 “手机号 / 邮箱 / 密码” 的多规则验证,但用策略模式可以轻松封装不同验证规则,实现 “规则可配置、可复用”。

// 策略模式封装验证规则(业务场景)
const validators = {
  phone: (val) => /^1[3-9]\d{9}$/.test(val) || '手机号格式错误',
  email: (val) => /^[\w-]+@\w+\.\w+$/.test(val) || '邮箱格式错误',
  // 更多规则...
};
// 通用验证函数(无需关心具体规则)
const validate = (type, val) => validators[type](val);

全局状态管理(非框架场景):如果项目不使用 Redux/Vuex,但需要跨组件共享状态(如用户信息、全局配置),用单例模式 + 观察者模式可以快速实现简易状态管理:

// 单例+观察者实现简易状态管理(业务场景)
const store = (() => {
  let state = { user: null };
  const listeners = [];
  return {
    getState: () => state,
    setState: (newState) => {
      state = { ...state, ...newState };
      listeners.forEach(fn => fn(state)); // 通知所有订阅者
    },
    subscribe: (fn) => listeners.push(fn)
  };
})();

2. 框架 API 的 “设计模式思维”

比如:
用 React 的useEffect时,本质是在使用观察者模式(监听状态变化并执行副作用);
封装 Vue 组件时,用props传递配置、emit触发事件,本质是策略模式(组件行为由外部策略配置);
开发 React 高阶组件(HOC)时,其实是在应用装饰器模式(给组件动态添加功能)。

3. 代码复用与可维护性

日常开发中,很多问题可以通过设计模式简化:
开发 “弹窗组件” 时,用单例模式避免重复创建 DOM(全局唯一弹窗);
开发 “表格组件” 时,用工厂模式根据列类型(文本、按钮、选择框)动态创建单元格;
开发 “权限控制” 时,用代理模式拦截组件渲染(无权限时显示占位内容)。

//代理模式实现权限控制的示例
// 1. 定义一个需要权限控制的原组件(示例:用户管理组件)
class UserManager {
  render() {
    return `
      <div class="user-manager">
        <h3>用户管理</h3>
        <button>添加用户</button>
        <button>删除用户</button>
      </div>
    `;
  }
}

// 2. 权限代理组件(核心:控制对原组件的访问)
class PermissionProxy {
  /**
   * 创建权限代理
   * @param {Object} component - 被代理的原组件
   * @param {string} requiredPermission - 所需权限标识(如 'user:manage')
   * @param {Array} userPermissions - 用户拥有的权限列表
   */
  constructor(component, requiredPermission, userPermissions) {
    this.component = component; // 原组件
    this.requiredPermission = requiredPermission; // 访问该组件需要的权限
    this.userPermissions = userPermissions; // 用户实际拥有的权限
  }

  // 代理渲染方法:拦截原组件的渲染,加入权限判断
  render() {
    // 检查用户是否有权限
    const hasPermission = this.userPermissions.includes(this.requiredPermission);
    
    if (hasPermission) {
      // 有权限:渲染原组件
      return this.component.render();
    } else {
      // 无权限:显示占位内容(提示无权限)
      return `
        <div class="permission-denied">
          <p>🔒 您没有 "${this.requiredPermission}" 权限,无法访问该功能</p>
        </div>
      `;
    }
  }
}

// 3. 使用示例
// 模拟用户权限(实际项目中从后端获取)
const userPermissions = ['user:view', 'post:manage']; // 该用户有"查看用户"和"管理帖子"权限

// 创建原组件
const rawUserManager = new UserManager();

// 用代理包装原组件,要求"user:manage"权限
const userManagerWithProxy = new PermissionProxy(
  rawUserManager, 
  'user:manage', // 访问用户管理需要的权限
  userPermissions
);

// 渲染组件(实际项目中会插入到DOM)
console.log('渲染结果:');
console.log(userManagerWithProxy.render());
// 输出:<div class="permission-denied">...</div>(因为用户没有'user:manage'权限)


// 扩展:代理模式的灵活性(无需修改原组件,可动态切换权限)
setTimeout(() => {
  console.log('\n3秒后权限更新:');
  // 动态添加权限(模拟权限变更场景)
  userPermissions.push('user:manage');
  console.log(userManagerWithProxy.render());
  // 输出:原UserManager组件的内容(有权限了)
}, 3000);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值