JS 技巧:设计模式(下)- 策略、装饰器、代理

在上篇内容中,我们介绍了单例、工厂、观察者三种常用JS设计模式,聚焦于"对象创建"和"对象间通信"的核心场景。本篇将继续讲解另外三种高频设计模式------策略模式、装饰器模式、代理模式,它们分别解决"算法灵活切换""功能动态扩展""对象访问控制"的问题,是JS开发中提升代码可维护性、可扩展性的关键技巧,尤其适配前端组件开发、业务逻辑封装等实际场景。

一、策略模式(Strategy Pattern)

1. 核心思想

定义一系列算法(策略),将每个算法封装成独立的对象,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。核心是"分离算法的定义与使用",避免大量if-else或switch-case判断,让代码更具灵活性和可维护性。

简单理解:就像去餐厅吃饭,"支付"是一个核心行为,而"微信支付""支付宝支付""现金支付"就是不同的策略,我们可以根据需求切换支付策略,无需修改"支付"的核心逻辑。

2. JS 实现(实战场景:表单验证)

表单验证是前端高频场景,不同字段(手机号、邮箱、密码)的验证规则不同,若用if-else判断会导致代码臃肿,用策略模式可完美解决。

javascript 复制代码
// 1. 定义策略对象(封装不同的验证算法)
const validateStrategies = {
  // 非空验证
  required: (value, msg) => {
    if (!value.trim()) return msg;
  },
  // 手机号验证
  phone: (value, msg) => {
    const reg = /^1[3-9]\d{9}$/;
    if (!reg.test(value)) return msg;
  },
  // 邮箱验证
  email: (value, msg) => {
    const reg = /^[\w-]+@[a-zA-Z0-9]+.[a-zA-Z]{2,4}$/;
    if (!reg.test(value)) return msg;
  },
  // 密码长度验证
  minLength: (value, msg, length) => {
    if (value.length < length) return msg;
  }
};

// 2. 定义上下文(使用策略的客户端)
class Validator {
  constructor() {
    this.strategies = []; // 存储当前需要执行的策略
  }

  // 添加验证规则(往策略列表中添加策略)
  add(value, strategyName, msg, ...params) {
    this.strategies.push(() => {
      // 执行对应的策略,返回错误信息
      return validateStrategies[strategyName](value, msg, ...params);
    });
  }

  // 执行所有验证策略,返回第一个错误信息
  validate() {
    for (const strategy of this.strategies) {
      const errorMsg = strategy();
      if (errorMsg) return errorMsg; // 有错误直接返回
    }
    return null; // 无错误返回null
  }
}

// 3. 实战使用
const validator = new Validator();
// 给手机号添加验证规则
validator.add('123456', 'required', '手机号不能为空');
validator.add('123456', 'phone', '手机号格式错误');
// 给密码添加验证规则
validator.add('123', 'required', '密码不能为空');
validator.add('123', 'minLength', '密码长度不能少于6位', 6);

// 执行验证
const error = validator.validate();
console.log(error); // 输出:手机号格式错误

3. 应用场景

  • 表单验证(不同字段的验证规则切换);
  • 排序算法切换(冒泡排序、快速排序、插入排序按需切换);
  • 支付方式、登录方式切换(微信、支付宝、QQ登录等);
  • 前端主题切换(浅色、深色、自定义主题)。

4. 优缺点

优点:算法可独立封装、灵活切换,避免冗余判断;代码扩展性强,新增策略无需修改原有逻辑(符合开闭原则);策略可复用。

缺点:策略数量过多时,会增加策略对象的维护成本;客户端需要了解所有策略的存在,才能选择合适的策略。

二、装饰器模式(Decorator Pattern)

1. 核心思想

动态地给一个对象添加额外的职责(功能),而不改变其原有的结构和核心逻辑。装饰器是一种"包装器",它包裹着原对象,在不破坏原对象的前提下,扩展其功能。

简单理解:就像给手机贴钢化膜、戴手机壳------手机的核心功能(通话、上网)不变,但新增了"防刮""防摔"的额外功能;装饰器就是"手机壳",原对象就是"手机"。

注意:JS中的装饰器(ES7提案)与传统装饰器模式原理一致,但语法更简洁,目前需通过Babel转译才能兼容低版本浏览器。

2. JS 实现(两种方式:传统方式 + ES7装饰器)

方式1:传统方式(手动封装装饰器)

javascript 复制代码
// 1. 原对象(核心功能)
class Coffee {
  // 核心方法:制作咖啡
  make() {
    console.log('制作一杯纯咖啡');
    return '纯咖啡';
  }
}

// 2. 装饰器1:加牛奶(扩展功能)
function addMilk(coffee) {
  // 保存原方法
  const originalMake = coffee.make;
  // 重写make方法,添加额外功能
  coffee.make = function() {
    const result = originalMake.call(this); // 执行原核心逻辑
    console.log('添加牛奶');
    return `${result} + 牛奶`;
  };
  return coffee;
}

// 3. 装饰器2:加糖浆(扩展功能)
function addSyrup(coffee) {
  const originalMake = coffee.make;
  coffee.make = function() {
    const result = originalMake.call(this);
    console.log('添加糖浆');
    return `${result} + 糖浆`;
  };
  return coffee;
}

// 4. 使用装饰器(动态扩展功能)
let myCoffee = new Coffee();
myCoffee = addMilk(myCoffee); // 给咖啡加牛奶
myCoffee = addSyrup(myCoffee); // 给咖啡加糖浆

myCoffee.make();
// 输出:
// 制作一杯纯咖啡
// 添加牛奶
// 添加糖浆

方式2:ES7装饰器(语法糖,更简洁)

javascript 复制代码
// 1. 定义装饰器函数(类装饰器)
function addMilk(target) {
  // target 是被装饰的类(Coffee)
  const originalMake = target.prototype.make;
  target.prototype.make = function() {
    const result = originalMake.call(this);
    console.log('添加牛奶');
    return `${result} + 牛奶`;
  };
}

// 2. 定义装饰器函数(可传参的装饰器)
function addSyrup(syrupType) {
  // 返回装饰器函数
  return function(target) {
    const originalMake = target.prototype.make;
    target.prototype.make = function() {
      const result = originalMake.call(this);
      console.log(`添加${syrupType}糖浆`);
      return `${result} + ${syrupType}糖浆`;
    };
  };
}

// 3. 使用装饰器装饰类
@addMilk // 无参装饰器
@addSyrup('草莓') // 有参装饰器(顺序:从下到上执行,先加糖浆,再加牛奶)
class Coffee {
  make() {
    console.log('制作一杯纯咖啡');
    return '纯咖啡';
  }
}

// 4. 使用
const myCoffee = new Coffee();
myCoffee.make();
// 输出:
// 制作一杯纯咖啡
// 添加草莓糖浆
// 添加牛奶

3. 应用场景

  • 前端组件功能扩展(如给按钮添加"加载中"状态、防抖节流功能);
  • 日志打印、性能监控(在不修改原函数的前提下,新增日志记录、耗时统计);
  • 权限控制(给需要权限的方法添加权限校验装饰器);
  • React/Vue中的高阶组件(HOC),本质就是装饰器模式的应用。

4. 优缺点

优点:不破坏原对象核心逻辑,动态扩展功能(符合开闭原则);装饰器可叠加使用,灵活性高;代码复用性强。

缺点:过多装饰器会增加代码复杂度,难以调试;ES7装饰器目前仍需转译,存在兼容性问题。

三、代理模式(Proxy Pattern)

1. 核心思想

为一个对象提供一个"代理"(中间层),通过代理对象控制对原对象的访问。代理对象可以在访问原对象前后,执行额外的逻辑(如拦截、过滤、缓存、权限校验),从而保护原对象、增强原对象的功能。

简单理解:就像房产中介------你(客户端)不直接接触房东(原对象),而是通过中介(代理对象)租房,中介可以帮你筛选房源、谈判价格(额外逻辑),同时保护房东的隐私(控制访问)。

JS中最常用的代理实现是 Proxy 对象(ES6新增),它原生支持代理功能,无需手动封装。

2. JS 实现(实战场景:数据拦截、缓存、权限控制)

场景1:数据拦截(监听对象属性的读写)

javascript 复制代码
// 1. 原对象(目标对象)
const user = {
  name: '张三',
  age: 20
};

// 2. 创建代理对象(中间层)
const userProxy = new Proxy(user, {
  // 拦截属性读取(get方法)
  get(target, prop) {
    console.log(`读取了${prop}属性,值为:${target[prop]}`);
    // 可添加额外逻辑,如默认值
    return target[prop] ?? '默认值';
  },
  // 拦截属性修改(set方法)
  set(target, prop, value) {
    console.log(`修改了${prop}属性,从${target[prop]}改为${value}`);
    // 可添加额外逻辑,如数据校验
    if (prop === 'age' && typeof value !== 'number') {
      console.error('年龄必须是数字');
      return false; // 阻止修改
    }
    target[prop] = value;
    return true; // 允许修改
  }
});

// 3. 通过代理对象访问原对象
console.log(userProxy.name); // 读取了name属性,值为:张三 → 张三
userProxy.age = 25; // 修改了age属性,从20改为25
userProxy.age = '26'; // 修改了age属性,从25改为26 → 年龄必须是数字(修改失败)

场景2:缓存代理(减少重复计算)

javascript 复制代码
// 1. 原函数(耗时计算函数)
function calculate(num) {
  console.log('执行了耗时计算');
  return num * 10; // 模拟耗时计算
}

// 2. 创建缓存代理
const calculateProxy = new Proxy(calculate, {
  cache: {}, // 缓存容器
  apply(target, thisArg, args) {
    const key = args.join('-'); // 用参数作为缓存key
    // 若缓存中存在,直接返回缓存值,无需重复计算
    if (this.cache[key]) {
      console.log('从缓存中获取结果');
      return this.cache[key];
    }
    // 若缓存中不存在,执行原函数,并存入缓存
    const result = target.apply(thisArg, args);
    this.cache[key] = result;
    return result;
  }
});

// 3. 使用代理函数
console.log(calculateProxy(5)); // 执行了耗时计算 → 50
console.log(calculateProxy(5)); // 从缓存中获取结果 → 50(无需重复计算)
console.log(calculateProxy(10)); // 执行了耗时计算 → 100

3. 应用场景

  • 数据监听(Vue3响应式原理的核心就是Proxy);
  • 缓存代理(减少重复请求、重复计算,提升性能);
  • 权限控制(拦截敏感操作,校验用户权限);
  • 远程代理(通过代理对象访问远程服务,隐藏远程服务的细节);
  • 防抖节流代理(给函数添加防抖节流功能,无需修改原函数)。

4. 优缺点

优点:保护原对象,控制访问权限;可在不修改原对象的前提下,新增额外逻辑(如缓存、拦截);解耦客户端与原对象,提升代码可维护性。

缺点:增加了代理层,可能会轻微影响性能;代理逻辑复杂时,会增加代码调试难度。

四、三种模式对比与总结

设计模式 核心解决问题 核心特点 关键区别
策略模式 算法灵活切换,避免冗余判断 封装算法,可相互替换 关注"算法本身的切换",客户端需选择策略
装饰器模式 动态扩展对象功能,不破坏原逻辑 包装原对象,功能可叠加 关注"功能扩展",不改变原对象的访问方式
代理模式 控制对原对象的访问,增强原对象 中间层拦截,保护原对象 关注"访问控制",客户端通过代理访问原对象

总结:三种模式均遵循"开闭原则"(对扩展开放,对修改关闭),是JS中提升代码质量的核心技巧。实际开发中,无需刻意套用模式,而是根据场景选择:

  • 需要切换算法/规则 → 策略模式;
  • 需要扩展功能且不破坏原逻辑 → 装饰器模式;
  • 需要控制访问、添加拦截逻辑 → 代理模式。
相关推荐
一颗小青松4 小时前
uniapp 集成友盟并且上传页面路径
前端·vue.js·uni-app
周淳APP4 小时前
微前端核心沙箱机制深度解析:从iframe到乾坤沙箱
前端·学习·iframe·微前端·qiankun·前端架构
JarvanMo4 小时前
Android View 相关工具包终于成为了历史
前端
2501_940041744 小时前
应用构建:前端复杂交互与数据可视化的进阶之路
前端·信息可视化
前端若水4 小时前
项目初始化:Vite + React + shadcn/ui
前端·react.js·ui
ZC跨境爬虫4 小时前
模块化烹饪小程序开发日记 Day4:网络层基础设施与接口治理实践
前端·javascript·数据库·ui·html
冴羽yayujs4 小时前
快速夯实 JavaScrilpt 基础的 33 个概念
前端·javascript·github·前端开发
放下华子我只抽RuiKe54 小时前
React 从入门到生产(二):状态与事件处理
前端·人工智能·深度学习·react.js·机器学习·前端框架·github
Maimai108084 小时前
React 项目目录结构怎么设计:从基础分层到真实业务落地
前端·javascript·react.js·microsoft·前端框架