JavaScript--设计模式
一、概述
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
二、原则
S – Single Responsibility Principle 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
O – OpenClosed Principle 开放/封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
L – Liskov Substitution Principle 里氏替换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
I – Interface Segregation Principle 接口隔离原则
- 保持接口的单一独立
- 类似单一职责原则,这里更关注接口
D – Dependency Inversion Principle 依赖倒转原则
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
三、分类
- 创建型
- 单例模式
- 原型模式
- 工厂模式
- 抽象工厂模式
- 建造者模式
- 结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
- 行为型
- 观察者模式
- 迭代器模式
- 策略模式
- 模板方法模式
- 职责链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介模式
- 解释器模式
四、示例
4.1单例模式
概述:
一个类只有一个实例(生成出来对象永远只有一个实例),并提供一个访问它的全局访问点。
实现过程:
- 构建一个函数返回一个对象实例
- 用一个声明一次变量来控制这个对象实例的生成。
- 如果该变量里已有一个对象,则直接返回,如果没有,则生成,生成给变量存起来。
应用:登录框
闭包实现
function single(){
let obj //标识
return function(){
if(!obj){ //判断是否为undefined
obj = new Object()
}
return obj
}
}
let singleObj = single()
let obj1 = singleObj()
let obj2 = singleObj()
console.log(obj1===obj2)//true
原型实现
function singlePrototype(){
if(!Object.prototype.instance){
Object.prototype.instance = new Object()
}
return Object.prototype.instance
}
let obj1 = singlePrototype()
let obj2 = singlePrototype()
console.log(obj1===obj2) //true
static实现
function singleStatic(){
if(!Object.instance){
Object.instance = new Object()
}
return Object.instance
}
let obj1 = singleStatic()
let obj2 = singleStatic()
console.log(obj1===obj2)
全局变量实现
function singleWindow(){
if(!window.instance){
window.instance = new Object()
}
return window.instance
}
let obj1 = singleWindow()
let obj2 = singleWindow()
console.log(obj1===obj2)
4.2工厂模式
概述:
工厂模式生产对象的,以一个工厂方法来生产对应的对象。
实现过程:
- 手动构建对象
- 手动给对象设置属性
- 手动返回对象
function factory(){
let obj = new Object()
obj.name = 'jack'
return obj
}
4.3组合模式
概述:
将对应多个相同名字方法 放在一个地方统一调用。
实现:
class SayHello{
constructor(){
}
say(){
console.log('hello')
}
}
class SayHi{
constructor(){
}
say(){
console.log('hi')
}
}
class SayBay{
constructor(){
}
say(){
console.log('baybay')
}
}
以上的三个类 分别都具备一个名为say的方法 如果需要调用的话 那么是一个个的对象进行调用而不能统一调用,如果我需要他统一调用,这个时候我们就可以使用组合模式。
class Combiner{
constructor(){
//容器来保存对应的对象
this.objs = []
}
push(obj){
//添加对象
this.objs.push(obj)
}
excute(fnName){
//执行对应的方法
this.objs.forEach(item=>{
item[fnName]()
})
}
}
//新建组合模式对象
let combiner = new Combiner()
//传入对应统一调用的对象
combiner.push(new SayHello())
combiner.push(new SayHi())
combiner.push(new SayBay())
//执行对应的方法
combiner.excute('say')
组合模式在vue中使用
use和install
vue.use()为注册全局插件所用,接收函数或者一个包含install属性的对象为参数,如果参数带有install就执行install, 如果没有就直接将参数当install执行, 第一个参数始终为vue对象, 注册过的插件不会重新注册
4.4观察者模式
概述:
- 观察者模式(obServer)他又被称为发布-订阅者模式,消息模式等。
- 定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己,当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
场景:
- DOM事件
document.body.addEventListener('click', function() {
console.log('hello world!');
});
document.body.click()
- vue响应式
实现:
class ObServer{
constructor(){
//事件和对应的处理函数存储的容器
this.arg = {} //click:[fn,fn1]
}
on(){//发布事件
}
emit(){//执行处理函数
}
off(){//取消事件
}
}
on方法实现
class ObServer{
constructor(){
this.arg = {} //{click:[fn,fn1]}
}
on(eventName, handler) { //发布事件 事件名 处理函数
if (!this.arg[eventName]) { //没有这个事件
this.arg[eventName] = [] //初始化里面为空数组
}
this.arg[eventName].push(handler) //将对应的函数追加
}
emit(){//执行处理函数
}
off(){//取消事件
}
}
emit方法实现
class ObServer{
constructor(){
this.arg = {} //{click:[fn,fn1]}
}
on(eventName, handler) { //发布事件 事件名 处理函数
if (!this.arg[eventName]) { //没有这个事件
this.arg[eventName] = [] //初始化里面为空数组
}
this.arg[eventName].push(handler) //将对应的函数追加
}
emit(eventName, params) { //执行处理函数
if (!this.arg[eventName]){
return
}
//会将里面的处理函数都执行
//遍历对应的处理函数数组
this.arg[eventName].forEach(fn => {
//将参数传入执行
fn.call(this, params)
})
}
off(){//取消事件
}
}
off方法实现
class ObServer {
constructor() {
this.arg = {} //{click:[fn,fn1]}
}
on(eventName, handler) { //发布事件 事件名 处理函数
if (!this.arg[eventName]) { //没有这个事件
this.arg[eventName] = [] //初始化里面为空数组
}
this.arg[eventName].push(handler) //将对应的函数追加
}
emit(eventName, params) { //执行处理函数
if (!this.arg[eventName]){
return
}
//会将里面的处理函数都执行
//遍历对应的处理函数数组
this.arg[eventName].forEach(fn => {
//将参数传入执行
fn.call(this, params)
})
}
off(eventName, handler) { //取消事件
if (!this.arg[eventName]) {
return
}
//将这个对应的fn删除
if (this.arg[eventName].length == 1) {
delete this.arg[eventName]
} else {
let i
this.arg[eventName].forEach((item, index) => {
if (Object.is(item, handler)) {
i = index
}
})
this.arg[eventName].splice(i, 1)
}
}
}
扩展:在观察者emit方法传入参数 传到对应的on里面的处理函数 vue里面子传父的实现
4.5代理模式
概述:
代理模式利用一个代理对象来处理当前对象事情
假设当A 在心情好的时候收到花,小明表白成功的几率有60%,而当A 在心情差的时候收到花,小明表白的成功率无限趋近于0。小明跟A 刚刚认识两天,还无法辨别A 什么时候心情好。如果不合时宜地把花送给A,花被直接扔掉的可能性很大,这束花可是小明吃了7 天泡面换来的。但是A 的朋友B 却很了解A,所以小明只管把花交给B,B 会监听A 的心情变化,然后选择A 心情好的时候把花转交给A,
es7新增一个类 Proxy 他就是用于代理的,他是vue3的底层实现
Proxy构造函数
new Proxy(目标对象,handler处理对象)
对应的处理对象有4大方法
- get属性 获取对应的代理对象的值调用
- set属性 设置代理对象的值调用
- deleteProperty 删除代理对象的属性调用
- has属性 在遍历的时候调用
//目标对象
let target = {name:'张三',age:18,say(){
console.log('hello');
}}
//利用proxy产生代理对象
let proxy = new Proxy(target,{
get(target,property,proxy){ //表示目标对象 表示属性名 表示代理对象
console.log('get调用了');
//访问值的时候
if(property =='name'){
return '我的名字是'+target[property]
}
if(property =='age'){
return '我的年纪是'+target[property]+'岁'
}
},
set(target,property,value){
//设置值的时候 进行相关操作
console.log(property);
console.log(value);
target[property] = value
},
deleteProperty(target,property,proxy){
//删除属性的时候
console.log('delete调用了');
delete target[property]
},
has(target,property){
//in的时候调用 必须返回boolean 强制转换为boolean类型
console.log('has调用了');
console.log(property);
return property in target
},
apply(target,property){ //函数调用触发
console.log('apply调用了');
}
})
//读取代理对象的属性的时候 会自动调用get方法 他的值是get方法返回的值
console.log(proxy.age); //调用get
proxy.name = 'jack' //调用set
console.log(proxy);
delete proxy.name //调用deleteProperty
console.log('name' in proxy); //某个东西是否在某个东西里面返回boolean
console.log(proxy.say); //代理只第一层
apply对应的方法
function sum(a, b) {
return a + b;
}
const handler = {
apply: function (target, thisArg, argumentsList) { //目标对象 当前this 参数数组
console.log('apply调用了');
// expected output: "Calculate sum: 1,2"
return target(argumentsList[0], argumentsList[1]) * 10;
}
};
const proxy1 = new Proxy(sum, handler);
console.log(sum(1, 2));
// expected output: 3
console.log(proxy1(1, 2));
// expected output: 30
4.6装饰者模式
概述:
- 动态地给某个对象添加一些额外的职责,,是一种实现继承的替代方案
- 在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象
实现:
//原本类
class Car{
constructor(){
}
run(){
console.log('车在跑')
}
}
//增强的类
class Decorater{
constructor(car){
this.car = car
}
run(){
console.log('我边吃饭边开车')
this.car.run()
}
}
new Decorater(new Car()).run()
扩展:es7新增一个装饰器 其实就是装饰器模式的封装
4.7适配器模式
概述:
将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。
实现:
let phone = {
fn(){
retrun '5v'
}
}
class Target{
constructor(){
}
fn(){
let v = phone.fn()
return '220转换为'+v
}
}
new Target().fn()
应用:
- 整合第三方SDK
- 封装旧接口