前端框架(vue)中常见的设计模式 | 青训营

76 阅读5分钟

1.工厂模式

1.1 概述

工厂模式是一种创建型设计模式,它通过封装对象的创建过程,将对象的实例化和具体业务逻辑解耦,提供一种灵活的方式来创建多个相似的对象。举个例子,假设正在开发一个电子产品商城,需要创建电视和手机两种产品,就可以使用一个工厂函数来创建这些产品:

// 工厂函数
function createProduct(type, name, price) {
  return {
    type,
    name,
    price,
    displayInfo() {
      console.log(`产品类型:${this.type},名称:${this.name},价格:${this.price} 元`);
    }
  };
}

// 通过工厂函数创建产品
const tv = createProduct('电视', '小米电视', 4999);
const phone = createProduct('手机', 'iPhone 14', 8699);

tv.displayInfo();    // 输出:产品类型:电视,名称:小米电视,价格:4999 元
phone.displayInfo(); // 输出:产品类型:手机,名称:iPhone 14,价格:8699 元

1.2 框架中的应用

在前端框架中,工厂模式的主要应用就是组件化。以vue举例,由于vue的任何组件都不超出html模板、数据选项、计算属性、生命周期等范围,也就是说存在相同的数据结构,就可以抽象出一个工厂函数帮我们通过这些配置项组装成完整的组件,这样每次写组件的时候就可以只写配置项,极大减少代码的冗余,实际上Vue.extend就是一个工厂函数。

// 配置项
const options = {
  template: '<div>{{ message }}</div>',
  data() {
    return {
      message: 'Hello, Factory!'
    };
  }
}
// 工厂函数
Vue.extend(options);

1.3 优点

  1. 封装对象创建:工厂模式将对象的创建过程封装在一个函数中,隐藏了具体的创建细节,使代码更加模块化和可维护。在Vue.js中,使用工厂模式可以将组件的创建逻辑集中在一个函数中,便于管理和拓展。
  2. 可复用和扩展性:通过工厂模式,可以轻松地创建多个具有相同结构和功能的对象。在Vue.js中,可以使用工厂模式创建多个相似的组件,提高代码的复用性和可扩展性。
  3. 解耦对象创建和使用:工厂模式将对象的创建过程和使用过程分离开来,降低了它们之间的耦合度。在Vue.js中,使用工厂模式创建组件可以将组件的创建细节与其使用的位置解耦,使代码更灵活。

1.4 缺点

  1. 可读性降低:在使用工厂模式时,需要理解和熟悉工厂函数或类的接口和使用方法。如果不熟悉工厂模式的开发人员阅读代码时可能会增加一些困惑。
  2. 难以追踪对象的创建:由于对象的创建逻辑被封装在工厂中,当需要跟踪对象的创建过程时,可能需要深入到工厂函数或方法中进行调试和分析,增加了调试的复杂性。

2.单例模式

2.1 概述

单例模式是一种创建型设计模式,它确保一个类只能存在一个实例,并提供了一个全局访问点以访问该实例,这个设计模式对于那些需要频繁使用相同对象实例的场景非常有用。

2.2 框架中的应用

在vue框架中,Vue实例就是一个单例,在组件中任何时候都可以通过 $root$parent 属性来访问该实例的数据或方法,并且Vue实例遵循单例模式的唯一实例性,所以可以将公用变量存放到Vue实例上供所有变量使用。

 mounted () {
    console.log(this.$root) // 根实例对象: {Vue#app}
 }

还有vue 的官方状态管理库Vuex,也是单例模式的应用,Vuex 的 store 对象就是一个单例,可以让多个组件共享数据。

// 创建store对象(唯一)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  },
  getters: {
    getCount(state) {
      return state.count
    }
  }
})

// 在组件中使用共享数据和方法
Vue.component('MyComponent', {
  template: '<div>{{ $store.getters.getCount }}</div>',
  mounted() {
    this.$store.dispatch('increment')
  }
})

2.3 优点

  1. 全局共享数据:单例模式可以用来管理全局共享的数据或状态,这样不同组件之间可以相互访问和修改这些数据,简化了组件之间的通信和数据传递过程。
  2. 简化状态管理:使用单例模式可以将状态集中管理,避免了在多个组件之间传递和同步状态的复杂性。这对于大型应用程序来说尤为有用,可以更轻松地追踪和维护状态的变化。
  3. 提高代码复用性:通过单例模式,可以将一些常用的数据或方法封装在单例对象中,并在多个组件中共享使用,提高了代码的复用性和可维护性。

2.4 缺点

  1. 潜在的状态共享问题:当多个组件依赖同一个单例对象时,可能会导致状态共享问题。如果不小心修改了单例对象中的数据,可能会影响到其他组件。
  2. 难以进行单元测试:由于单例对象是全局共享的,它的行为可能会对组件之间的交互产生影响,使得单元测试变得困难。

3.观察者模式

3.1 概述

观察者模式用于在对象之间建立一种一对多的依赖关系。当一个对象(称为主题或可观察者)的状态发生变化时,所有依赖于它的对象(称为观察者)都会得到通知并自动更新。

3.2 框架中的应用

在vue框架中,响应式数据侦听器(watch) 就是观察者模式的主要应用。具体思路是,在数据对象中通过Object.definePropertyProxy对属性进行劫持,当属性被访问或修改时,自动通知依赖更新。vue使用了三个关键角色:Observer(观察者)、Dep(依赖)和Watcher(观察者),它们协同工作来实现观察者模式:

  • Observer(观察者):Observer会递归地遍历数据对象的所有属性,并使用Object.definePropertyProxy对属性进行劫持。在属性的getter函数中收集依赖,在setter函数中触发依赖更新通知。
  • Dep(依赖):每个响应式属性都有一个对应的Dep实例,用于管理依赖关系。Dep内部维护了一个存储Watcher的数组,每个Watcher都是一个观察者。当属性被读取时,会触发Dep的依赖收集过程,将当前的Watcher添加到依赖列表中。当属性被修改时,会触发Dep的notify方法,通知所有依赖的Watcher进行更新。
  • Watcher(观察者):每个组件都有一个Watcher实例与其对应,Watcher会在初始化过程中对模板中的表达式进行求值,并在求值过程中触发依赖的getter操作。这样,Watcher就会被添加到响应式属性的Dep中,建立起观察者与依赖之间的关系。当依赖发生变化时,Watcher会被通知,从而触发重新渲染组件的操作。

通过这种方式,Vue能够自动追踪数据对象的变化,并在数据变化时通知相关的观察者进行更新,使得视图与数据同步。

// 点击按钮修改数据,vue就会监听到值的变化,从而更新视图中p标签中的内容
<template>
    <p>{{ message }}</p>
    <button @click="updateMessage">更新数据</button>
</template><script>
export default {
    data: {
      message: 'Hello, world' // 初始状态
    },
    methods: {
      updateMessage() {
        this.message = 'Hello, Vue'; // 修改状态
      }
    }
}
</script>

3.3 优点

  1. 响应式:Vue通过观察者模式实现了数据的响应式更新,当数据发生变化时,相关的视图会自动更新,简化了手动操作的过程。
  2. 解耦性:观察者模式能够将观察者和被观察者解耦,它们之间不直接依赖,降低了组件之间的耦合度,使系统更加灵活可扩展。
  3. 可维护性:通过观察者模式,每个组件只需要关注自己的逻辑,代码分离清晰,易于维护和扩展。

3.4 缺点

  1. 内存泄漏:如果没有正确处理观察者对象的释放,可能会导致内存泄漏问题。当一个观察者已经被销毁,但仍然保存在被观察者中,这样就会造成资源浪费。
  2. 性能问题:观察者模式中的频繁更新可能会影响系统的性能。如果存在大量的观察者和被观察者,或者有频繁的数据变化,可能导致性能下降。