今天,咱们来看一下 JS 中的常用设计模式。主要有 5 种:
- 单例模式
- 观察者模式
- 工厂模式
- 模块模式
- 装饰器模式
一、单例模式
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
在 JavaScript 中,单例模式通常用于 创建唯一的对象实例,例如全局配置、共享资源或状态管理器。这样可以避免重复创建实例,节省资源,并确保全局数据的一致性。
在前端框架中的应用
- Vue.js: 在 Vue 中,单例模式通常用来创建 Vuex store(状态管理器),它确保整个应用程序只有一个 store 实例
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
}
});
export default store;
- React: 在 React 中,类似的单例模式可以用于创建全局状态管理库,如 Redux 或者 React Context API。Redux 的 store 也是一个单例,确保整个应用使用的是同一个状态树。
// store.js
import { createStore } from 'redux';
const initialState = { count: 0 };
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
const store = createStore(reducer);
export default store;
单例模式的基本实现
通过闭包来实现单例模式:
const Singleton = (function () {
let instance;
function createInstance() {
return { name: "张三" };
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// 使用示例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
适用场景
- 全局配置: 当你需要一个全局唯一的配置对象,供整个应用程序使用时,比如应用的配置设置、日志系统等。
- 状态管理: 当你需要一个全局的状态管理工具(如 Vuex 或 Redux)来管理应用程序的状态,确保状态的一致性和集中管理。
二、观察者模式
观察者模式用于定义对象之间的一对多依赖关系。
当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这个模式通常用于实现发布-订阅(pub-sub)机制。
主要优点在于它提供了一个解耦的方式来管理对象之间的依赖关系。
在前端框架中的应用
- Vue.js: Vue2 的响应式系统就是基于观察者模式的。当 Vue 组件的依赖数据发生变化时,相关组件会被重新渲染。
// Vue 组件示例
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
- React: React 使用了类似于观察者模式的机制来实现组件的重新渲染。当组件的状态(state)或属性(props)发生变化时,React 会重新渲染相关的组件。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hello React!' };
}
render() {
return <div>{this.state.message}</div>;
}
}
观察者模式的基本实现
通过事件监听机制来实现:
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notifyObservers(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log(`数据为: ${data}`);
}
}
适用场景
- 事件处理: 当需要处理用户交互、系统事件或其他异步事件时。例如,监听按钮点击事件或处理服务器推送的数据。
- 数据绑定: 在构建数据驱动的用户界面时(类似 vue),例如:表单数据和视图之间的双向绑定,观察者模式可以帮助自动更新视图。
- 发布-订阅系统: 当需要构建一个松耦合的系统,其中多个组件可以订阅和发布事件时,观察者模式是一个很好的选择。例如,实现聊天系统中的消息通知功能。
三、工厂模式
工厂模式是一种创建型设计模式,用于定义一个创建对象的接口,但让子类决定实例化哪个类。
工厂模式通过将对象的创建过程封装到一个工厂类中,使得代码可以在不暴露对象创建逻辑的情况下使用这些对象。主要优点在于对象的创建和使用之间的耦合度降低。
在前端框架中的应用
- Vue.js: Vue 的组件系统可以被视为工厂模式的一种实现。在 Vue 中,你可以使用工厂函数来创建不同的组件实例。例如,Vue 的 Vue.component 方法实际上就是一个工厂方法,它用来注册和创建组件。
// 注册一个全局组件
Vue.component('my-component', {
template: '<div>Hello World</div>'
});
// 创建组件实例
new Vue({
el: '#app',
template: '<my-component></my-component>'
});
- React: React 的组件创建和管理也是工厂模式的一种应用。你可以定义组件类或函数组件,然后 React 会根据需要创建和管理这些组件的实例。
function MyComponent() {
return <div>Hello World</div>;
}
// 使用组件
ReactDOM.render(<MyComponent />, document.getElementById('root'));
工厂模式的基本实现
工厂模式可以通过函数或类来实现:
// 工厂函数
function Car(make, model) {
this.make = make;
this.model = model;
}
function createCar(make, model) {
return new Car(make, model);
}
// 使用示例
const car1 = createCar('理想', 'L9');
const car2 = createCar('问界', 'M9');
console.log(car1); // Car { make: '理想', model: 'L9' }
console.log(car2); // Car { make: '问界', model: 'M9' }
适用场景
- 对象创建逻辑复杂: 当对象的创建过程复杂,涉及到多个步骤或配置时,工厂模式可以将这些细节封装起来。
- 对象实例化的变体: 当需要创建不同类型的对象,但对象的创建过程基本相似时,可以使用工厂模式来简化创建过程。例如,根据不同的配置创建不同类型的组件或模块。
- 依赖注入: 当需要在创建对象时注入不同的依赖项或配置时,工厂模式可以帮助管理这些依赖项并确保对象的正确初始化。
- 解耦: 当需要将对象的创建和使用解耦时,工厂模式可以提供一个统一的创建接口,使得客户端代码不需要关心对象的具体创建过程。
四、模块模式
模块模式用于创建一个具有私有和公有方法的模块,并封装模块的内部状态。这种模式在前端的框架或者项目中是使用最广的。
在前端框架中的应用
- Vue.js: Vue.js 中的单文件组件(.vue 文件)实际上就是模块化的方案,它允许将模板、脚本和样式封装在一个文件中,并通过 export 和 import 语法来管理这些模块
<!-- MyComponent.vue -->
<template>
<div>{{message}}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
};
}
};
</script>
<style>
div {
color: red;
}
</style>
- React: 在 React 中,组件也是一种模块化的实践。每个组件通常都放在一个单独的文件中,这样可以封装组件的状态和逻辑。React 使用 ES6 的 import 和 export 来管理组件模块。
// MyComponent.js
import React from 'react';
function MyComponent() {
return <div>Hello, React!</div>;
}
export default MyComponent;
// App.js
import React from 'react';
// 模块化
import MyComponent from './MyComponent';
function App() {
return (
<div>
<MyComponent />
</div>
);
}
export default App;
模块模式的基本实现
在 JavaScript 中,模块模式通常通过立即调用的函数表达式(IIFE)来实现。这种模式能够封装模块的私有变量和方法,同时暴露一些公有接口给外部使用。
const myModule = (function() {
// 私有变量和方法
let privateVar = '我是私有的';
function privateMethod() {
console.log(privateVar);
}
// 公有变量和方法
return {
publicVar: '我是公有的',
publicMethod: function() {
privateMethod();
}
};
})();
适用场景
- 封装和组织代码: 当你需要封装内部逻辑,避免全局命名冲突,并将功能模块化时,可以使用模块模式。
- 私有和公有方法: 当需要隐藏模块的私有方法和变量,仅暴露必要的公有接口时,模块模式提供了一种清晰的方式来实现这一点。
- 代码复用: 当你希望将功能或组件拆分成独立的模块,以便在不同的地方复用时,模块模式可以帮助你更好地组织和管理这些模块。
五、装饰器模式
装饰器模式是一种结构型设计模式,用于动态地为对象添加额外的功能,而不修改其结构。它通常会通过创建一个装饰器对象来包装原始对象,并在装饰器对象中添加或修改功能。
在前端框架中的应用
在 Vue 或者 react 中,虽然没有直接使用装饰器模式,但有类似的概念,如 Vue 的 mixins 和 React 的高阶组件。
- Vue.js: Vue 的 mixins 功能可以看作是一种装饰器模式的实现。Mixins 允许你将可复用的功能代码抽象成一个对象,并将其混入到 Vue 组件中,以便共享和复用。
// 定义一个 mixin
const myMixin = {
data() {
return {
mixinData: '我是 mixin 的数据'
};
},
methods: {
mixinMethod() {
console.log('我是 mixin 的方法');
}
}
};
// 使用 mixin
new Vue({
el: '#app',
mixins: [myMixin],
created() {
this.mixinMethod(); // '我是 mixin 的数据'
}
});
- React: 在 React 中,装饰器模式也不是直接使用的,但高阶组件(HOC)可以看作是一种装饰器模式的应用。HOC 是一个函数,接受一个组件并返回一个新的组件,该新组件增加了额外的功能或数据。
// 高阶组件
function withExtraProps(WrappedComponent) {
return function EnhancedComponent(props) {
return <WrappedComponent {...props} extraProp="extra" />;
};
}
// 原始组件
function MyComponent(props) {
return <div>{props.extraProp}</div>;
}
// 使用高阶组件
const EnhancedComponent = withExtraProps(MyComponent);
ReactDOM.render(<EnhancedComponent />, document.getElementById('root'));
装饰器模式的基本实现
装饰器模式可以通过高阶函数或类装饰器来实现:
// 原始对象
class Coffee {
cost() {
return 5;
}
}
// 装饰器函数
function MilkDecorator(coffee) {
const originalCost = coffee.cost();
coffee.cost = function() {
return originalCost + 2;
};
return coffee;
}
// 使用示例
const myCoffee = new Coffee();
console.log(myCoffee.cost()); // 5
const myMilkCoffee = MilkDecorator(myCoffee);
console.log(myMilkCoffee.cost()); // 7
适用场景
- 动态扩展功能: 当你需要在运行时动态地为对象添加功能,而不修改对象的结构时,装饰器模式提供了一种灵活的方式来实现这一点。
- 组合功能: 当你需要组合多个装饰器来创建一个具有多个功能的对象时,装饰器模式提供了一种清晰的方式来实现这一点。