设计模式可以提高代码的可扩展性、可维护性和可测试性。不同的前端框架可能会有不同的设计模式的实现方式,但这些模式都是为了解决前端开发中常见问题而采用的。这里以 Vue 为例讲一下前端框架中存在的几种设计模式。
代理模式
Vue2.x 的响应式实现是代理的典型使用,利用了 Object.defineProperty 内置的 getter 和 setter 实现数据的监听和劫持,从而根据预设逻辑映射到真实 DOM,实现视图的更新,下面是 defineProperty() 方法使用的一个例子:
Object.defineProperty(obj,'age',{//添加一个属性
value:12,//数据描述符
writable:true,//不可变特性类似于const
enumerable:true,//可枚举,针对for...in等枚举方法设置
configurable:true//默认false,表示对象属性是否可删,以及除 value 和 writable 特性外的其他特性是否可以被修改
})
//实际上提供了一种数据劫持的方法
Object.defineProperty(obj,'age',{//添加一个属性
get() {//存取描述符
// 直接返回age
return age;
},
set(val) {
// 赋值时触发,将值赋予变量age
age = val;
}
})
尽管 vue3 中响应式的原理不同,采用 Proxy 和 Reflect,但本质上也是代理。
单例模式
在 Vue 框架中能找到很多单例模式的影子,但是最明显的例子恐怕是在组件的创建和管理过程中。大多数真实的应用都是由一棵嵌套的、可重用的组件树组成的。我们使用 Vue 的构造函数创建一个根实例,在后面添加组件的过程中,尽管每个组件都会有自己的实例,但只有根组件的实例是唯一的,负责管理其他组件的实例,根实例包含全局状态、路由信息以及事件总线等共享资源,其他组件通过根实例来访问这些资源:
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
观察者模式(发布订阅模式)
发布订阅模式在 Vue 中也非常常见,其实组件自定义事件实现父子组件间通信的方式就是发布订阅的典型案例。子组件若需向父组件传递消息,父组件先绑定一个自定义事件,再由子组件触发自定义事件并传输数据。
除此外,利用 pubsub-js 实现的任意组件通信也使用了发布订阅模式,尽管不是 Vue 原生的通信方式,但是包含 Vue 在内的很多框架使用者都会用到这个包:
引入订阅的一方:import pubsub from 'pubsub-js';
接收数据的一方,在组件内订阅消息,消息的回调留在自身:
methods(){
//假如不想接收消息名可以使用_,占位
demo(_,data){......}
}
......
mounted(){
//作为消息回调能接收到两个参数,第一个是消息名称,
this.pid = pubsub.subscribe('xxx',this.demo);//订阅消息
},
//最好在 beforeDestroy 钩子中,用 `pubsub.unsubscribe(this.pid)` 取消订阅
beforeDestroy(){
pubsub.unsubscribe(this.pid);
}
提供数据的一方::PubSub.publish('xxx', 数据)
工厂模式
工厂模式简单来讲就是封装了实例的构造函数,对外暴露这个构造函数接口,通过不同的输入来创建不同类型的实例。vue 创建路由的过程就是使用了工厂模式:
export default class VueRouter {
constructor(options) {
this.mode = mode // 路由模式
switch (mode) { // 简单工厂
case 'history': // history 方式
this.history = new HTML5History(this, options.base)
break
case 'hash': // hash 方式
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract': // abstract 方式
this.history = new AbstractHistory(this, options.base)
break
default:
// ... 初始化失败报错
}
}
}
工厂模式将路由创建的具体过程封装了起来,对外暴露一个构造函数,从而允许我们定义不同类型的路由组件:
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
上面介绍了 Vue 中出现的一些设计模式,实际上 Vue 还使用了很多其他设计模式,体会这些设计模式的精髓有助于我们构建灵活健壮的应用。