手撕前端7大设计模式!从Vue到Axios,你的代码质量为何能翻倍提升?

作者:佚名 时间:2025-11-12 09:49

字号

就我自身来讲,身为长期关注开发者生态的科技类媒体工作者,我留意到设计模式这个极为经典的论题,开始再度在前端领域被重视起来。随着前端应用复杂程度不停增加,如何在快速更新换代的时候,保持代码的可维护状态,这已经成为工程师们必须要面对的和日常相关的课题。现在,我们打算从实际应用方面,分析前端开发中最具实际有效价值的设计模式。

工厂模式

工厂模式,是一种能让代码有着更优可读性、更具可维护性的模式,这种模式靠封装对象创建过程来达成效果。在Vue项目开发进程里,创建有相似属性的组件配置对象时,我们常借助工厂函数处理,避免重复代码出现。2023年GitHub统计显示,采用工厂模式的项目,代码复用率提高约30% 。

Axios库中的create方法,是工厂模式极具典型性的示例体现。开发者可通过axios.create()这个手段,构建出带有自定义配置情况的实例,每一个这样的实例有着单独的拦截器设置以及超时规划方面。像这样的办法,使得在不相同的业务模块中使用不同的请求配置,变得便捷且明晰。

// 模拟Axios工厂:根据配置创建请求实例
class AxiosFactory {
  // 静态工厂方法
  static create(config = {}) {
    // 默认配置
    const defaultConfig = {
      baseURL: '',
      timeout: 5000,
      headers: { 'Content-Type': 'application/json' }
    };
    // 合并用户配置与默认配置
    const finalConfig = { ...defaultConfig, ...config };
    // 封装请求方法
    const request = async (options) => {
      const { url, method = 'GET', data = {} } = options;
      try {
        const response = await fetch(`${finalConfig.baseURL}${url}`, {
          method,
          headers: finalConfig.headers,
          timeout: finalConfig.timeout,
          body: method === 'POST' ? JSON.stringify(data) : null
        });
        return await response.json();
      } catch (err) {
        console.error('请求失败:', err);
        throw err;
      }
    };
    // 返回封装后的请求实例
    return {
      get: (url, params) => request({ url, params }),
      post: (url, data) => request({ url, method: 'POST', data })
    };
  }
}
// 使用工厂创建实例
const userApi = AxiosFactory.create({ baseURL: 'https://api.user.com' });
const orderApi = AxiosFactory.create({ baseURL: 'https://api.order.com', timeout: 10000 });
// 调用实例方法(无需关心内部实现)
userApi.get('/info');
orderApi.post('/create', { goodsId: 123 });

单例模式

一个类仅拥有唯一的一个实例,这是由单例模式来保证的,它还给出了全局可访问的点。在前端开发当中,Vuex 状态管理库属于单例模式典型的应用示例。无论组件于何处引入 store,所访问的都是同一个状态实例,像这样就确保了数据的一致性。

class Singleton {
  // 1. 用私有属性(#开头)存储唯一实例,外部无法直接访问
  static #instance;
  // 2. 私有构造函数:防止外部通过new创建实例
  constructor() {
    // 若已有实例,直接抛出错误(严格限制单例)
    if (Singleton.#instance) {
      throw new Error('单例模式不允许重复创建实例,请通过getInstance()获取');
    }
  }
  // 3. 静态方法:全局访问点,确保只创建一个实例
  static getInstance() {
    // 首次调用时创建实例,后续直接返回已存在的实例
    if (!Singleton.#instance) {
      Singleton.#instance = new Singleton();
    }
    return Singleton.#instance;
  }
  // 示例:单例的业务方法
  doSomething() {
    console.log('单例实例的业务逻辑');
  }
}
// 测试:多次调用获取的是同一个实例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
// 错误用法:外部无法通过new创建实例
// const instance3 = new Singleton(); // 抛出错误

对于浏览器环境里的window对象来讲,还有全局事件总线这种情形,都是呈现出单例模式的实际例子。在大型单页样式的应用之内,借助使用此类模式的办法去管理全局设置或者共享服务之事,是能够切实有效地避免资源无端耗用以及数据出现不一致的状况等这些问题的,从而提升应用的性能时效呢。

观察者模式

依靠目标对象状态改变时,依存于它的所有观察者会自动接收通知这种方式,来建立对象多个依存关系的,叫观察者模式,在用户界面开发里,此项模式能达成数据与视图自动同步,显得特别重要。这种模式下,目标对象状态一旦改变,依存的所有观察者都会自动收到通知 。

在实际开展开发的那段时期内,我们可以依仗成就简易形式的EventBus来以此运用观察者模式。依靠定义像on、emit以及off等这类方法,从而搭建起组件相互之间的通信机制。这种方法在那并非是父子关系的组件展开通信的情景当中特别有用,能够促使代码结构变成为更加清晰 。

class EventBus {
  // 私有属性:存储事件与对应的观察者(回调函数)
  #handlers = {}; // 结构:{ 事件名: [callback1, callback2, ...] }
  // 1. 订阅事件:添加观察者
  $on(eventName, callback) {
    if (typeof callback !== 'function') {
      throw new Error('回调函数必须是function类型');
    }
    // 若事件不存在,初始化空数组
    if (!this.#handlers[eventName]) {
      this.#handlers[eventName] = [];
    }
    // 添加回调到事件列表
    this.#handlers[eventName].push(callback);
  }
  // 2. 触发事件:通知所有观察者
  $emit(eventName, ...args) {
    // 若事件无订阅者,直接返回
    const callbacks = this.#handlers[eventName] || [];
    // 执行所有回调,并传递参数
    callbacks.forEach(callback => callback(...args));
  }
  // 3. 取消订阅:移除观察者
  $off(eventName) {
    if (eventName) {
      // 移除指定事件的所有订阅者
      this.#handlers[eventName] = [];
    } else {
      // 若未传事件名,清空所有订阅
      this.#handlers = {};
    }
  }
  // 4. 一次性订阅:触发后自动取消订阅
  $once(eventName, callback) {
    // 封装回调:执行后立即取消订阅
    const wrapper = (...args) => {
      callback(...args); // 执行原回调
      this.$off(eventName); // 取消订阅
    };
    this.$on(eventName, wrapper);
  }
}
// 使用示例
const bus = new EventBus();
// 订阅事件
bus.$on('userLogin', (username) => {
  console.log(`欢迎 ${username} 登录`);
});
// 一次性订阅
bus.$once('showTip', (msg) => {
  console.log('提示:', msg);
});
// 触发事件
bus.$emit('userLogin', '张三'); // 输出:欢迎 张三 登录
bus.$emit('showTip', '请完善个人资料'); // 输出:提示:请完善个人资料
bus.$emit('showTip', '再次触发'); // 无输出(已取消订阅)
// 取消订阅
bus.$off('userLogin');
bus.$emit('userLogin', '李四'); // 无输出(已取消订阅)

发布订阅模式

事件中心被引入后,发布订阅模式达成了发布者与订阅者的彻底解耦,它不同于观察者模式,于发布订阅模式中,发布者和订阅者不必知道对方的存在,只需通过事件中心来进行通信。

在前端生态系统当中,Vue的全局事件总线属于发布订阅模式的具体展现形式,组件能够借助$on来订阅特定的事件,然而其他组件是经过$emit去发布事件的,这样的机制使得组件之间的通信更具备灵活性,并且还降低了代码的耦合程度。

原型模式

于JavaScript语言之中,有着一类核心特性要提及,这特性便是针对借助已然存在对象予以复制,进而来创建新对象的原型模型。在这种模式的情形下,每一组JavaScript对象都拥有一条原型链路,也是鉴于从_proto_继承这样的方式,从而变成了属性以及方法能够实现共享的状态。

// 1. 定义原型对象(被复制的模板)
const userPrototype = {
  // 原型方法
  greet() {
    console.log(`Hello, 我是 ${this.name},年龄 ${this.age}`);
  },
  // 原型属性
  role: 'user'
};
// 2. 基于原型创建新对象(方式1:Object.create)
const user1 = Object.create(userPrototype);
// 为新对象添加自身属性
user1.name = '张三';
user1.age = 20;
user1.greet(); // 输出:Hello, 我是 张三,年龄 20
console.log(user1.role); // 输出:user(继承自原型)
// 3. 基于原型创建新对象(方式2:手动实现原型链)
function createUser(name, age) {
  const user = {};
  // 设置原型:让新对象继承userPrototype
  Object.setPrototypeOf(user, userPrototype);
  // 添加自身属性
  user.name = name;
  user.age = age;
  return user;
}
const user2 = createUser('李四', 22);
user2.greet(); // 输出:Hello, 我是 李四,年龄 22
console.log(user2.__proto__ === userPrototype); // true(验证原型链)

真实开展开发工作的时段当中,我们是可以借助Object.create()这种方式于现存对象的基础之上创建出全新对象的。ES6所引入的class语法虽说给出了更为传统的面向对象编程路径,可是其底层还是基于原型机制的。对于把握JavaScript语言特性而言,理解原型模式是极为关键重要的。

代理模式

给出一个替代品或者占位符针对对象,这是代理模式里的做法,以此去控制对原对象的访问,在现代前端开发这个范围里,Proxy对象在数据验证、缓存以及访问控制等场景方面被广泛应用 。

html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>缓存代理:API请求优化title>
head>
<body>
  <h1>城市查询(缓存代理演示)h1>
  <input type="text" class="query-input" placeholder="输入省份名查询城市(如:广东省)">
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">script>
  <script>
    // 1. 定义缓存对象:存储已查询的结果
    const cache = {};
    // 2. 代理函数:封装API请求,添加缓存逻辑
    async function searchCity(provinceName) {
      // 若缓存中存在,直接返回缓存结果
      if (cache[provinceName]) {
        console.log('从缓存中获取数据');
        return cache[provinceName];
      }
      // 若缓存中不存在,发起API请求
      console.log('从API获取数据');
      try {
        const response = await axios({
          url: 'http://hmajax.itheima.net/api/city',
          params: { pname: provinceName }
        });
        // 缓存结果(键:省份名,值:城市列表)
        cache[provinceName] = response.data.list;
        return response.data.list;
      } catch (err) {
        console.error('查询失败:', err);
        throw err;
      }
    }
    // 3. 绑定DOM事件:用户输入回车后查询
    document.querySelector('.query-input').addEventListener('keyup', async (e) => {
      if (e.keyCode === 13) { // 按下回车键
        const province = e.target.value.trim();
        if (!province) return;
        const cities = await searchCity(province);
        console.log('查询结果:', cities);
      }
    });
  script>
body>
html>

依托Proxy予以实现的,便是Vue这个框架在其3版本时所具备的响应式系统,针对原始数据进行操作的状态拦截,是借助代理对象来达成的,如此一来,数据本身的响应式更新就能够就此得以完成,较之于另外一个版本Vue2里基于Object.defineProperty的那种实现方式而言,这样依托Proxy实现的这种响应式系统机制,在灵活性以及强大程度方面,都有着更为突出的表现 。

那些从事开发工作岗位的人们,于真实的项目推进进程里,你们最为频繁运用的是哪一种用于设计的模式呀?欢迎在评论的范围区域内去分享你拥有实际操作经验的过往经历哦,同时也绝对不要忘掉去点赞以及分享这一篇文章以便让更多同样从事相关工作的人员得以看到 。

责任编辑:CQITer新闻报料:400-888-8888   本站原创,未经授权不得转载
继续阅读
热新闻
推荐
关于我们联系我们免责声明隐私政策 友情链接