什么是设计者模式
- 设计模式是用于解决某个问题的一种固定模式(原理都是一样的),不区分语言。
- 常用的设计模式有23种,主要分为三大类:创建型、结构型、行为型
设计模式分类
- 创建型 (创建对象):单例模式、工厂模式
- 结构型(将多个小结构并入一个大结构):组合模式、代理模式、装饰器模型
- 行为型 (对应类和对象的行为进行相关处理):观察者模式
设计模式的原则
- 开闭原则:软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改
- 里式置换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 单一职责原则:一个类只负责一项职责
- 依赖倒置原则:依赖于抽象,不依赖于具体
- 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
- 迪米特原则:又称为最少知道原则,表示一个对象应该对其它对象保持最少的了解,只与直接的朋友通信
- 合成复用原则 CRP:在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分; 新的对象通过向这些对象的委派达到复用已有功能的目的
工厂模式
以工厂的形式来生产对象的,不关注对象细节
function factory(name) {
//创建对象
var obj = {}
// 对象赋属值
obj.name = name
// 返回
return obj
}
let obj = factory("tom")
console.log(obj);
单例模式(饿汉 、懒汉)
保证产生的对象只有一个 (不会被污染)
- 利用闭包实现
//利用闭包实现
function closeSingleton() {
var single = null
return function() {
// 判断对应的对象是否为null
// 如果为null则产生新对象
if (!single) {
single = new Object()
}
return single
}
}
var obj = closeSingleton()
var single = obj()
var single1 = obj()
console.log(single == single1); // true
- 利用原型实现
//原型实现:对象.prototype.方法
function prototypeSingleton() {
if (!prototypeSingleton.prototype.single) {
// 在prototypeSingleton原型链上查找是否该方法,没有则创建
prototypeSingleton.prototype.single = new Object()
}
return prototypeSingleton.prototype.single
}
var single = prototypeSingleton()
var single1 = prototypeSingleton()
console.log(single == single1);
- 利用静态属性实现
//使用静态方法:对象.方法
function staticSingleton() {
if (!staticSingleton.single) {
staticSingleton.single = new Object()
}
return staticSingleton.single
}
var single = staticSingleton()
var single1 = staticSingleton()
console.log(single == single1);
- 使用global对象实现
//使用全局变量:this指向全局
function globalSingleton() {
if (!globalThis.single) {
//globalThis表示global的指向,这里表示global指向window,也就是全局
globalThis.single = new Object()
}
return globalThis.single
}
var single2 = globalSingleton()
var single3 = globalSingleton()
console.log(single2 == single3);
组合模式
将多个小结构组合成一个大结构 (将共有的函数放在一起调用)
function Fly() {
this.init = () => {
console.log('先飞呀');
}
}
function Run() {
this.init = () => {
console.log('后跑呀');
}
}
function Jump() {
this.init = () => {
console.log('再跳呀');
}
}
//普通调用
new Fly().init()
new Run().init()
new Jump().init()
//封装一个函数来调用
function Action() {
this.obj = []
//传入对应的函数对象
this.add = function(...args) {
this.obj = this.obj.concat([...args])
}
// 执行对应函数的init方法
this.exec = function(fnName) {
// 遍历对应的对象数组,调用里面的相关方法
this.obj.forEach((args) => {
// 调用对应的方法
args[fnName].call(this)
})
}
}
let action = new Action()
// 添加函数事件
action.add(new Fly(), new Jump(), new Run())
// 调用对应的方法
action.exec('init')
组合模式的应用,模拟vue中的use和install
class Vue {
//解析对应的对象 执行对应的install
static use(...objs) {
objs = objs.map(v => {
//如果没有install方法
if (!v.install) {
if (typeof v != 'function') {
throw new Error('传入内容出错')
}
//将本身当作insatll函数
let fn = v
v = {
install() {
fn()
}
}
}
return v
})
Vue.exec(objs)
}
//传入对象进行执行
static exec(arr) {
arr.forEach(obj => {
obj['install'].call(this, Vue)
});
}
}
Vue.use({
install() {
console.log('吃饭了么');
}
}, {
install() {
console.log('你好啊');
}
})
Vue.use({
install(vue) {
console.log('嘿嘿');
}
})
装饰器模式
用一个新的类将对应的原本的对象进行包装再进行加强(在不改变原有对象的基础上增强对象)
// 在不改变原有对象的基础上,增强对应的对象
//原有的类
function Person() {
this.run = function() {
console.log('跑');
}
}
//增强 类
function Stronger(person) {
this.person = person
this.run = function() {
this.person.run()
console.log("飞翔");
}
}
let person = new Person()
person.run() //原有类的调用
let stronger = new Stronger(person)
stronger.run() //增强类调用原有类
Ts内置有对应的装饰器 Decorator TS中使用注解来进行对应的装饰器添加 @decorator
代理模式
概述
代理模式是在原有对象的基础上利用代理对象来增强对应的对象 ,代理对象通常访问的是实际的对象。
示例
我(原本的对象)请了会计(代理),会计给我管钱 (功能增强),当会计(代理)把钱花完了(我(原本对象)的钱也没有)
- 代理对象对原本的对象进行了功能增强
- 代理对象影响的是实际的对象
- ES7新增对应的Proxy的类来帮助我们进行代理
Proxy (vue3的底层实现)
实例化 (传入对应的被代理对象,处理对象,产生一个代理对象)
var proxy = new Proxy ( target , handler )
// 在不改变原有对象的基础上,增强对应的对象,代理对象通常访问的是实际的对象
// 使用es7新增的proxy类来实现
var obj = { //obj是被代理对象
name: 'tom',
age: 20
}
var proxy = new Proxy(obj, {
//获取相关属性:被代理对象、属性名、代理对象
get(targetObj, attributeName, proxyObj) {
// console.log(arguments);
if (attributeName == 'name') {
return '姓名为' + targetObj[attributeName]
} else if (attributeName == 'age') {
return targetObj[attributeName] + '岁'
}
return targetObj[attributeName]
},
//设置相关属性:被代理对象、属性名、属性值、代理对象
set(targetObj, attributeName, attributeValue, proxyObj) {
targetObj[attributeName] = attributeValue
},
//定义属性和属性值
// defineProperty(targetObj, attributeName, attributeValue, proxyObj) {
// Object.defineProperty(proxyObj, 'sex', {
// configurable: true, //是否可以删除
// enumerable: true, //是否可以遍历
// value: '男', //属性值
// writable: true //是否可以改变
// })
// },
//删除属性和属性值
deleteProperty(targetObj, attributeName, proxyObj) {
delete targetObj[attributeName]
},
//目标对象 对应的属性 默认返回false 使用in关键词调用
has(targetObj,p){
console.log(targetObj,p)
return p in targetObj
},
})
console.log(proxy.age); // 20 调用get
console.log(proxy.name); // tom 调用get
proxy.name = 'marry' //设置name
proxy.age = 10 //设置
console.log(proxy); // Proxy {name: 'marry', age: 10} 调用get
console.log(obj); //{name: 'marry', age: 10} 原来的被代理对象也发生改变
console.log('name' in proxy) // true 调用has
delete proxy.name // 删除了obj中的name
console.log(obj); //{age: 10}
proxy的handler相关属性方法
- get ( 被代理对象、属性名、代理对象 ) ——访问属性的时候调用
- set ( 被代理对象、属性名、代理对象 ) ——设置属性的时候调用
- defineProperty ( 被代理对象、属性名、属性值、代理对象) ——定义属性的时候调用
- deleteProperty ( 被代理对象、属性名、代理对象) ——删除属性的时候调用
- has ( 被代理对象、属性名) ——判断是否存在,返回一个布尔值。
观察者模式(obServer、发布者-订阅者模式)
观察者模式是前端最常用的模式,它相当于对应的监听和处理执行的机制。
观察者模式的核心内容
- 事件发布者:发布者
- 事件监听者:订阅者
- 事件处理:相关处理
【注意】
- 事件名和处理函数的关系
一对多 :一个事件可以有多个处理函数、click [ handler1 , handler2 ]
- 发布者和事件名的关系
一对多 :一个发布者可以发布多个事件、 { 事件名 : [ 处理函数 ] }
常见的发布订阅者模式 —— addEventListener (事件监听器)
//element.addEventListener(事件名,handler)
box.addEventListener('click',function(){
//处理
console.log('处理了')
})
模仿eventListener来实现发布订阅者模式
class Observer {
constructor() {
//存储事件和处理函数的容器{click:[handler],mosuedown:[handler]}
this.events = {}
}
//添加监听事件
on(eventName, handler) {
//判断是否存储了对应的事件
if (eventName in this.events) {
//如果存储了就给他加到对应的处理函数数组内
this.events[eventName].add(handler)
} else {
//如果没有的话先需要开劈一个数组 将处理函数装入
this.events[eventName] = new Set([handler])
}
}
//执行事件
emit(eventName, ...args) {
// 判断该事件是否存在
if (!this.events[eventName]) {
return
}
//遍历集合,执行处理函数
this.events[eventName].forEach(handler => {
//调用对应的函数传递参数
handler.apply(this, args)
});
}
//删除监听事件
off(eventName, handler) {
//查询是否具备对应的事件
//不具备结束对应的事件
if (!this.events[eventName]) {
return
}
//具备的话 删除对应的事件
this.events[eventName].delete(handler)
}
}
let observer = new Observer()
observer.on('click', function() { //添加监听事件并直接调用
console.log('点击了')
})
observer.off('click', function() { //无法删除
console.log('点击了')
})
observer.on('click', handler) //添加监听事件
// observer.off('click', handler) //可以删除
function handler(arg) {
console.log('点击了1' + arg)
}
observer.emit('click', '哈哈哈') //调用执行方法
【总结】
- off 是用于取消事件
- on 是用于监听事件
- emit 是用于执行事件,传入的参数可以被对应的 on 接收
- off 调用一定要 emit 之前
- 存储使用的对象进行存储(key:事件名,value:处理函数集合)
- 观察者模式是 vue2 底层实现之一 (对应的数据双向绑定必须使用观察者模式)