设计模式
设计模式是一个抽象的概念,是软件开发过程中的对于问题的一般解决思路
工厂模式
- 定义:工厂模式是用来创建对象的一种常见的设计模式,不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数可以视为一个工厂。
- 简单工厂模式:只需要在工厂函数中传入一个options,即可生成所需的实例
function Animal(options) {
let obj = {};
obj.color = options.color;
obj.name = options.name;
obj.getInfo = function () {
return 'name :' + obj.name + ',color:' + obj.color;
}
return obj
}
let cat = Animal({ name: '小猫', color: 'white' });
console.log(cat.getInfo())
- 工厂方法:抽象类只用于子类的继承,实例化对象的工厂继承抽象类,用于实例化对象
//定义一个抽象类,只用于子类的继承
class User {
constructor(name = '', viewPage = '') {
//通过new.target检测User是否通过new关键字被调用
if (new.target === User) {
throw new Error('抽象类不能实例化')
}
this.name = name;
this.viewPage = viewPage;
}
}
//定义一个实例化对象的工厂,只用于实例化对象
class UserFactory extends User {
//继承抽象类中的参数
constructor(name, viewPage) {
super(name, viewPage)
}
//一个创建实例的方法,通过传入的不同参数创建不同的实例
create(role) {
switch (role) {
case 'superAdmin':
return new UserFactory('超级管理员', ['首页', '通讯录', '发现', '应用数据'])
case 'admin':
return new UserFactory('普通管理员', ['首页', '通讯录', '发现'])
case 'user':
return new UserFactory('普通用户', ['首页', '发现'])
default:
throw new Error('参数错误,可选参数:superAdmin,admin,user')
}
}
}
//通过工厂方法创建实例
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
console.log(superAdmin)
- 抽象工厂模式:不直接生成实例,而是用于对产品类的创建
//抽象工厂模式:不直接生成实例,而是用于对产品类的创建
function getAbstractUserFactory(type) {
switch (type) {
case 'wechat':
return UserOfWechat;
case 'QQ':
return UserOfQQ;
case 'weibo':
return UserOfWeibo;
default:
throw new Error('参数错误')
}
}
//生成不同的产品类
let WechatUserClass = getAbstractUserFactory('wechat')
let QQUserClass = getAbstractUserFactory('QQ');
//不同的用户实例
let wechatUser = new WechatUserClass('user1');
单例模式
- 定义:保证一个类中仅有一个实例,并且能够提供一个全局访问点能够访问到这个实例。
- 场景:
- 例如windows回收站,任务管理器等,每次只能打开一个
- 每次点击登陆按钮,不论点击多少次,只会出现一个登陆弹窗。
- 防抖函数中每次只会设置一个定时器
- Vue在安装插件的时候只能被安装一次,因为install方法中,进行了判断,如果已经有Vue实例,就不会重新install。
- 使用静态类class实现单例模式
- constructor中定义唯一实例的属性和方法
- 提供一个全局访问点进行创建:通过静态类创建一个生成实力的方法,如果没有生成实例,就通过new关键字创建实例,如果有,就直接返回这个实例
//使用静态class实现单例模式
class SingleClass {
constructor(name, creator, products) {
this.name = name;
this.creator = creator;
this.products = products;
}
//通过静态类创建唯一的实例
static getInstance(name, creator, products) {
if (!this.instance) {
this.instance = new SingleClass(name, creator, products)
}
return this.instance
}
}
//通过instance创建实例,整个类只能被创建一个实例。
let apple = SingleClass.getInstance('apple', 'a', 'iphone');
let copy = SingleClass.getInstance('applecopy', 'b', 'bphone');
console.log(apple, copy)
- 通过闭包实现单例模式
- 创建一个single函数,把传入的函数变为只能执行一次的那种
- 创建一个res指向null
- return function,判断res是否是null?如果是就执行函数,并且执行结果赋值给res,不是就返回结果。
- 因为内部函数引用了外层作用域的变量,因此变量res一直存在不会被回收,因此可以用来记录是否执行过函数。
//实现单例模式:通过闭包实现
function Single(fn) {
//闭包存储结果,下次调用时仍然保存在内存中,所以可以进行判断
let res = null
return function () {
//如果没有res,就res=fn(),如果有就返回res
return res == null ? (res = fn()) : res;
}
}
//函数:生成一个登陆弹窗
function createLogin() {
let div = document.createElement('div');
div.innerHTML = '这是一个登陆弹窗,只出现一次';
div.style.display = 'none';
document.body.append(div);
return div;
}
//将函数用Single包裹,使得这个函数只能生成一次
console.log(Single(createLogin))
代理模式
- 定义:使用一个代理对象,来操作对象中的属性。如果一个对象不适合直接引用另一个对象,代理对象可以在两个对象之间起到中介的作用。
- 场景:例如Vue3实现响应式,就是利用Proxy实现的
- 实现:ES6中的Proxy在目标对象之前多一层拦截,访问对象触发getter,增加或者修改对象触发setter,删除对象触发deleteProperty
let obj = {
lxy:'lxy',
hsh:'hsh',
}
let p = new Proxy(obj,{
get(target,key,receiver){
console.log(`访问到${key},${target}`);
return Reflect.get(target,key,receiver)
},
set(target,key,value){
console.log(`修改了${target},${key},${value}`)
return Reflect.set(target,key,value)
},
deleteProperty(target,key){
console.log('删除了'+target,key)
return Reflect.deleteProperty(target,key)
}
})
p.lxy
p.hsh = 'pig'
delete p.hsh
console.log(obj)
观察者模式
- 定义:观察者模式定义了对象间一对多的依赖关系,当目标对象(被观察者)的状态发生改变时,所有依赖它的对象都会得到通知。
- 使用场景:
- 例如Vue实现响应式的依赖收集,然后当数据改变时,会通知所有的watcher去更新视图,就是利用观察者模式实现的。
- 实现一个观察者模式
- 创建两个类:一个是被观察者(目标对象),一个是观察者
- 向被观察者身上添加一个观察者数组。并且创建一个方法用于向数组中添加观察者,一个方法用于从数组中移除观察者,一个方法用于通知所有观察者进行视图更新
- 观察者有名称,以及一个进行更新的方法
//被观察者,目标类
class Subject{
//向被观察者身上添加一个观察者数组
constructor(){
this.observers = []
}
//用于向观察者数组中添加观察者
add(observer){
this.observers.push(observer)
}
//用于从观察者数组中移除观察者
remove(observer){
const index = this.observers.findIndex(o=>o.name === observer.name)
this.observers.splice(index,1);
}
//通知,执行所有observes的update方法
notify(){
const observers = this.observers;
observers.forEach(observer=>{
observer.update()
})
}
}
//观察者类
class Observer{
constructor(name){
this.name = name;
}
update(){
console.log('去更新视图了啦~')
}
}
//创建一个被观察者对象
let sub = new Subject()
let observer1= new Observer('lxy');
let observer2 = new Observer('hsh');
sub.add(observer1)
sub.add(observer2)
sub.notify()
发布订阅模式
- 定义:发布订阅模式实现了对象间多对多的依赖关系,通过事件中心管理多个事件。发布者不会直接将消息发送给订阅者,而是将事件放入事件中心(消息队列)中,由订阅者接收感兴趣的事件。
- 实现发布订阅模式
- $on('事件名',fn(){回调函数}),意思是如果接收到了'事件名',就执行回调函数
- $emit('事件名'),传递一个''事件'
- $off('事件名'),移除这个事件
class Observer {
constructor() {
//一个消息队列,对象
this.message = {}
}
//on方法接收一个事件名,一个回调函数,并且往消息队列中添加属性名为事件名,属性值为由回调函数组成的数组
$on(type, fn) {
//如果消息队列中有type事件名,就向里面添加事件
//如果消息队列中没有事件名,就初始化一个数组
if (!this.message[type]) {
this.message[type] = [fn]
} else {
this.message[type].push(fn);
}
}
$off(type, fn) {
//如果没有type,return
if (!this.message[type]) return
//如果有type,没有fn,直接删除整个事件
if (!fn) {
this.message[type] = undefined;
return
}
//如果有type,有fn,就只删除一个fn
this.message[type] = this.message[type].filter((item) => item !== fn)
}
$emit(type, ...args) {
//遍历消息队列,然后执行每一项
//判断有没有
if (!this.message[type]) return
this.message[type].forEach(element => {
element(...args);
});
}
$once(type, fn) {
function cb() {
fn();
this.$off(type, cb)
}
this.$on(type, cb);
}
}
适配器模式
- 定义:用于解决两个接口不兼容的情况,不需要改变已有的接口,而是通过给接口包装一层的方法实现两个接口兼容。
- 场景:例如,百度地图和谷歌地图两个地图的渲染方法方法名不同,不能通过相同的渲染接口进行渲染,这时可以在不改变渲染方法的情况下,增加一个适配器接口
let googleMap = {
show(){
console.log('开始渲染谷歌地图')
}
}
let baiduMap = {
display(){
console.log('开始渲染谷歌地图')
}
}
//已有的渲染接口
function renderMap(map){
if(map.show instanceof Function){
map.show();
}
}
renderMap(baiduMap);
//不能通过渲染接口调用baiduMap
renderMap(googleMap)
//在不改变地图接口的情况下,可以增加一个baiduMap的适配器接口
let baiduMapAdapter = {
show(){
return baiduMap.display();
}
}
//这样就可以通过rendermap调用两个接口了
装饰模式
- 定义:装饰模式不需要改变已有的接口,实现给对象添加额外的功能
- 实现:使用ES7中的装饰器语法实现
function readonly(target,key,descriptor){
descriptor.writable = false
return descriptor;
}
class Test{
@readonly
name = 'lxy'
}
let t = new Test()
t.name = '111'