讲一下前端用过的设计模式,附带使用场景
1. 构造器模式+原型模式
构造器模式和原型模式可以用来批量创建对象,只需要每次实例化他就行了。
构造函数创建的对象实例是通过 new
关键字调用构造函数生成的,它们不会挂载在原型上。构造函数创建的每个对象实例都会有自己的独立属性,因此它们需要单独的内存空间来存储这些属性。
然而,构造函数本身是可以挂载在原型上的。在 JavaScript 中,每个函数都有一个原型对象(prototype
)。通过将方法和属性添加到构造函数的原型上,可以使所有由该构造函数创建的对象实例共享这些方法和属性,从而节省内存空间。
下面是一个示例,展示了如何将方法挂载在构造函数的原型上:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
};
var person1 = new Person('John');
var person2 = new Person('Jane');
person1.sayHello(); // 输出:Hello, my name is John
person2.sayHello(); // 输出:Hello, my name is Jane
在这个示例中,Person
构造函数有一个 name
属性,并且我们将 sayHello
方法挂载在了构造函数的原型上。这样,所有由 Person
构造函数创建的对象实例都可以共享 sayHello
方法,而不需要重复在每个对象实例上创建一个新的函数。这样可以节省内存空间,并且使代码更高效。
进阶-->为了使代码更加简洁,可以用ES6的class来创建对象。
在 ES6 中引入了类(class)的概念,它提供了更简洁和面向对象的语法来创建对象。使用类创建对象的过程中,构造函数和原型的概念被封装在类的语法中。
下面是使用类创建对象的示例:
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello, my name is ' + this.name);
}
}
const person1 = new Person('John');
const person2 = new Person('Jane');
person1.sayHello(); // 输出:Hello, my name is John
person2.sayHello(); // 输出:Hello, my name is Jane
在这个示例中,Person
类代替了构造函数,并且 constructor
方法在类中用于初始化对象的属性。类中的其他方法(如 sayHello
)将自动挂载在类的原型上,因此所有由类创建的对象实例都可以共享这些方法。
应用场景
:多Tab切换的场景。
下面是一个组件封装:
<template>
<div class="tabs-component">
<div>
<span v-for="tab in tabs" :key="tab.id" class="tab-link" @click="changeTab(tab.id)">{{ tab.title }}</span>
</div>
<div>
<div v-for="tab in tabs" :key="tab.id" :class="['tab', { 'active': currentTab === tab.id }]">
<h2>{{ tab.title }}</h2>
<p>{{ tab.content }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
tabsData: {
type: Array,
required: true,
},
},
data() {
return {
tabs: this.tabsData,
currentTab: 1,
};
},
methods: {
changeTab(tabId) {
this.currentTab = tabId;
},
},
};
</script>
<style scoped>
.tabs-component {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
}
.tab {
display: none;
background-color: #ffffff;
padding: 10px;
margin-top: 10px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tab.active {
display: block;
}
.tab-link {
cursor: pointer;
margin-right: 10px;
}
</style>
在上述示例中,我们将标签页组件封装为一个Vue组件,并通过props属性接收名为tabsData
的属性,该属性是一个数组类型,并且是必需的。
在组件的data中,我们使用tabsData
来初始化tabs
数组,并使用currentTab
来追踪当前选中的标签页。
通过将tabsData
传递给组件的属性来使用该组件,并传递不同的标签页数据:
<template>
<div>
<tabs-component :tabsData="tabs1"></tabs-component>
<tabs-component :tabsData="tabs2"></tabs-component>
</div>
</template>
<script>
import TabsComponent from './TabsComponent.vue';
export default {
components: {
TabsComponent,
},
data() {
return {
tabs1: [
{ id: 1, title: 'Tab 1', content: 'Content for Tab 1' },
{ id: 2, title: 'Tab 2', content: 'Content for Tab 2' },
],
tabs2: [
{ id: 1, title: 'Tab A', content: 'Content for Tab A' },
{ id: 2, title: 'Tab B', content: 'Content for Tab B' },
{ id: 3, title: 'Tab C', content: 'Content for Tab C' },
],
};
},
};
</script>
在这个示例中,我们在父组件中使用tabs-component
组件两次,并通过不同的tabsData
属性传递不同的标签页数据。
这样,每次使用tabs-component
组件时,你可以传递不同的值作为标签页数据,以实现组件的复用并满足定制化的需求。
2. 工厂模式
举一个简单的例子,我们去餐馆吃饭,只需要按照菜单上的菜名进行点餐,然后菜做出来之后,我们不需要知道这些菜是怎么做的,只管负责吃就好了。在这里面,餐馆就相当于工厂,负责生产菜品,访问者通过餐馆就可以拿到产品。
这个例子有一个特点,访问者只需要产品名就可以从工厂获得实例,访问者不需要关心实例创建的过程。
工厂模式其实就是将创建对象的过程单独封装,它的目的,就是为了实现无脑传参。
2.1 简单工厂模式
//User类
class User {
//构造器
constructor(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
//静态方法
static getInstance(role) {
switch (role) {
case 'superAdmin':
return new User({ name: '超级管理员', viewPage: ['首页', '应用数据', '权限管理'] });
break;
case 'admin':
return new User({ name: '管理员', viewPage: ['首页', '应用数据'] });
break;
case 'user':
return new User({ name: '普通用户', viewPage: ['首页'] });
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
}
// 实例化对象
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');
User就是一个简单工厂,在该函数中有3个实例中分别对应不同的权限的用户。当我们调用工厂函数时,只需要传递superAdmin, admin, user这三个可选参数中的一个获取对应的实例对象。
简单工厂模式的优势就在于,只需要一个参数,就可以获得所需的对象,无需知道对象创建的具体细节。但是,在函数内部包含了对象所有的创建逻辑,和判断逻辑的代码,如果我们的判断很多,或者代码逻辑很复杂的情况下,这样工厂函数就会变的很复杂,很庞大,难以维护。所以,简单工厂只适合以下情况:
- 创建的对象数量较少
- 创建的对象的逻辑不是很复杂
3. 单例模式
在Vue中,单例模式可以在以下场景中使用:
- 全局状态管理:Vue应用中通常需要管理全局状态,例如用户登录状态、主题配置、语言设置等。单例模式可以用来创建一个全局的状态管理对象,确保在整个应用中只有一个实例存在,以便在不同的组件之间共享和修改状态数据。
- 资源管理:在Vue应用中,有时需要管理全局的资源对象,如网络请求库、消息通知对象等。通过使用单例模式,可以创建一个全局的资源管理对象,以确保在应用的不同部分共享同一个资源实例,避免重复创建和浪费资源。
- 事件总线:Vue中的组件通信可以通过事件总线机制来实现。单例模式可以用来创建一个全局的事件总线对象,用于在不同组件之间发送和接收事件,实现组件之间的解耦和通信。
- 工具类:在Vue应用中,有时需要使用一些工具函数或工具类来执行一些通用的功能。单例模式可以用来创建一个全局的工具类实例,确保在应用的任何地方都可以访问和使用这些工具函数或方法。
需要注意的是,在Vue应用中,可以使用Vue的插件机制来创建单例对象。通过将单例对象作为Vue插件进行注册,可以方便地在整个应用中使用和访问该对象。
总的来说,单例模式在Vue中适用于需要创建全局唯一实例的场景,以便在应用的不同部分共享和访问该实例。它可以提供一种方便且可维护的方式来管理全局状态、资源、事件和工具等对象。
举例:axios请求封装
// 创建 API 请求封装类
class API {
// ... 其他代码 ...
// 封装 GET 请求方法
async get(endpoint, params = {}) {
try {
const response = await axiosInstance.get(endpoint, { params });
// 处理响应数据
return response.data;
} catch (error) {
// 处理请求错误
throw error;
}
}
// 封装 POST 请求方法
async post(endpoint, data = {}) {
try {
const response = await axiosInstance.post(endpoint, data);
// 处理响应数据
return response.data;
} catch (error) {
// 处理请求错误
throw error;
}
}
// 添加请求拦截器
addRequestInterceptor(onFulfilled, onRejected) {
return axiosInstance.interceptors.request.use(onFulfilled, onRejected);
}
// 添加响应拦截器
addResponseInterceptor(onFulfilled, onRejected) {
return axiosInstance.interceptors.response.use(onFulfilled, onRejected);
}
// 移除请求拦截器
removeRequestInterceptor(interceptorId) {
axiosInstance.interceptors.request.eject(interceptorId);
}
// 移除响应拦截器
removeResponseInterceptor(interceptorId) {
axiosInstance.interceptors.response.eject(interceptorId);
}
}
在上述示例中,我们添加了 get
和 post
方法,分别封装了 GET 和 POST 请求。这些方法接受不同的参数,并使用 axiosInstance
发起相应类型的请求。
另外,我们还添加了 addRequestInterceptor
、addResponseInterceptor
、removeRequestInterceptor
和 removeResponseInterceptor
方法,用于添加和移除请求拦截器和响应拦截器。
使用这些扩展后的方法,你可以方便地发送不同类型的请求,并对请求和响应进行拦截和处理。以下是一个示例使用这些扩展方法的代码:
// 使用示例
const api = API.getInstance();
// 发起 GET 请求
api.get('/users', { page: 1, limit: 10 })
.then(data => {
// 处理响应数据
console.log(data);
})
.catch(error => {
// 处理请求错误
console.error(error);
});
// 发起 POST 请求
api.post('/users', { name: 'John', age: 30 })
.then(data => {
// 处理响应数据
console.log(data);
})
.catch(error => {
// 处理请求错误
console.error(error);
});
// 添加请求拦截器
const requestInterceptorId = api.addRequestInterceptor(
config => {
// 修改请求配置,添加请求头等
return config;
},
error => {
// 处理请求错误
throw error;
}
);
// 添加响应拦截器
const responseInterceptorId = api.addResponseInterceptor(
response => {
// 处理响应数据,进行统一的格式化等操作
return response;
},
error => {
// 处理响应错误
throw error;
}
);
// 在需要移除拦截器时调用对应的方法
api.removeRequestInterceptor(requestInterceptorId);
api.removeResponseInterceptor(responseInterceptorId);
通过使用这些扩展方法,你可以更灵活地定制和管理请求,以及对请求和响应进行拦截和处理,以满足具体的项目需求。
举例: 实现全局弹窗,vuex状态机等
4. 观察者模式
从上图我们可以看出,vue是通过Object.defineProperty实现对数据的劫持,然后中间做了一个中转,最后渲染到vue层。
我们可以肯定的是,vue.js借鉴了观察者模式,但是我感觉还是有点区别的,观察者模式跟注重的是事件驱动,比如我买房这个动作,第一次和销售了解可能没有合适的房源,然后销售就会跟你说: ‘如果有合适的房源我们会第一时间通知你',当有新房源的时候他会给你打电话通知你,这一系列的根源是买房这个事件,他驱动了整套流程。而vue我们都知道是数据驱动,也就是只有data里的值发生改变的话,Object.defineProperty才会对他劫持,从而去更新dom,触发视图的更新。
转载自:https://juejin.cn/post/7243680457815932965