js设计模式

文章目录

面向对象

特性

  • 继承:子类继承父类
  • 封装:数据的权限和保密(把对象和方法保密起来不让人看见,有些可以开放出去让人看见)
  • 多态:同一接口不同实现

public完全开放、 protected对子类开放(自己放出去不行)、 private对自己开放(谁访问都不行)

为什么有面向对象?

为了数据结构化(程序的执行:顺序、判断、循环–结构化),对于计算机,结构化的才是最简单的,编程应该简单加抽象(抽象完的才会简单)

六大原则solid:

1. 单一原则single responsibility

定义:每个模块只专注自己的功能,一个类应该只负责一项职责
简单说就是让一个模块的责任尽量少,如果发现一个模块功能过多,就应该拆分为多个模块,让一个模块专注一个功能,更有利于代码维护

体现:微服务,把不同的程序逻辑装到不同的服务去,通过这些服务的组装,形成一个大的服务,这些微服务之间也能达到一个解耦,就是各自可以用不同的技术栈来实现,但是组装起来就能成为一个大的服务,达到各个模块之间的分治,能够各自迭代不会受到其他地方的影响。

2. 里氏替换原则liskov substitution(LSP)

定义:子类可以扩展父类的功能,但不能改变原有父类的功能(多态)。继承时,子类尽量不要重写和重载父类的方法

【扩展】继承
优点:

  • 提高代码重用性,子类拥有父类的方法和属性;
  • 提高代码可扩展性,子类可形似于父类,但异于父类,保留自我的特性;

缺点:侵入性、不够灵活、高耦合

  • 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
  • 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成非常糟糕的结果,要重构大量的代码。

3. 依赖倒置原则dependence inversion

要面向接口编程(多态),不要面向实现编程

高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。也就是说要面向接口编程,不能面向实现编程。

4. 接口隔离原则interface segregation

要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用
理解:

  • 保持单一接口;
  • 复杂的接口,根据业务拆分成多个简单接口(降低依赖,降低耦合);(对于有些业务的拆分多看看适配器的应用)
    【接口的设计粒度越小,系统越灵活,但是灵活的同时结构复杂性提高,开发难度也会变大,维护性降低】

5. 迪米特原则/最小知晓原则law of demeter(LOD)

定义:低耦合,一个对象应该对其他对象保持最少的了解(中介者模式就是这个的应用)

核心思想: 类间解耦(低耦合,高内聚。只有使各个模块之间的耦合尽量的低,才能提高代码的复用率)
通俗来讲: 一个类对自己依赖的类知道的越少越好。只与你直接的朋友通信,而避免和陌生人通信

6. 开闭原则open closed

定义:对扩展开放,对修改关闭。鼓励去扩展程序,避免修改原有程序,会导致出现问题
webpack提供loader的概念,就是他能够打包各种后缀名的模块,并且不同的后缀名都有不同的loader去处理它,这样子就能够支持无数种后缀名的打包。不会修改webpack源码,保证webpack运行过程的稳定性。

设计模式

创建型

1. 工厂模式Factory

当一个函数返回一个对象时,我们称之他为 工厂函数
提供创建对象的接口,把成员对象的创建工作转交给一个外部对象,好处在于消除对象之间的耦合(也就是相互影响)

实例化对象不使用new,用工厂方法代替(将new操作单独封装)

作用:实现创建者和调用者的分离
分类:
简单(静态)工厂模式:通过接收不同的参数,返回不同的对象实例。弊端:增加一个新产品,如果不修改代码,没办法实现新增产品
工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
抽象工厂模式:围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂

小结:
简单工厂:虽然某种程度上不符合设计原则,但实际使用最多(不符合开闭原则)
工厂方法:不修改已有类的前提下,通过增加新的工厂类实现扩展
抽象工厂:不可以增加产品,可以增加产品族
在这里插入图片描述
简单(静态)工厂模式:
弊端:增加一个新产品,需要改工厂代码

class WuLing{
    getInfor(){
        console.log('五菱宏光')
    }
}

class Tesla{
    getInfor(){
        console.log('特斯拉')
    }
}

class CarFactory{
    // 方法一
    getCar(car){
        if(car === '五菱'){
            return new WuLing();
        }else if(car === '特斯拉'){
            return new Tesla()
        }else{
            return null
        }
    }
    // 方法二
    // getWuling(){
    //     return new WuLing();
    // }
    // getTesla(){
    //     return new Tesla()
    // }
}

const consumer = new CarFactory
consumer.getCar('五菱').getInfor()

工厂方法模式:

class WuLing{
    getInfor(){
        console.log('五菱宏光')
    }
}

class Tesla{
    getInfor(){
        console.log('特斯拉')
    }
}

class Maserati{
    getInfor(){
        console.log('玛莎拉蒂')
    }
}

// 五菱工厂
class WuLingFactory{
    getCar(){
        return new WuLing();
    }
}

// 特斯拉工厂
class TeslaFactory{
    getCar(){
        return new Tesla();
    }
}

// 玛莎拉蒂工厂
class maseratiFactory{
    getCar(){
        return new Maserati();
    }
}

class CarFactory{
}

const car1= new WuLingFactory().getCar()
const car2= new TeslaFactory().getCar()
const car3= new maseratiFactory().getCar()
car1.getInfor()
car2.getInfor()
car3.getInfor()
2. 抽象工厂模式Abstract Factory

在这里插入图片描述

    // 手机产品接口
    class IphoneProduct{
        start(){}
        shutdown(){}
        callup(){}
        sendSMS(){}  
    }

    // 路由产品接口
    class RouterProduct{
      start(){}
      shutdown(){}
      openwifi(){}
      setting(){}  
    }

    // 小米手机工厂
    class XiaomiPhone extends IphoneProduct{
        start(){
            console.log('开启小米手机')
        }
        shutdown(){
            console.log('关闭小米手机')
        }
        callup(){
            console.log('小米打电话')
        }
        sendSMS(){
            console.log('小米发短信')
        }  
    }

    // 小米路由器工厂
    class XiaomiRouter extends RouterProduct{
        start(){
            console.log('开启小米路由器')
        }
        shutdown(){
            console.log('关闭小米路由器')
        }
        openwifi(){
            console.log('打开小米路由器')
        }
        setting(){
            console.log('设置小米路由器')
        }  
    }

    // 华为手机工厂
    class HuaweiPhone extends IphoneProduct{
        start(){
            console.log('开启华为手机')
        }
        shutdown(){
            console.log('关闭华为手机')
        }
        callup(){
            console.log('华为打电话')
        }
        sendSMS(){
            console.log('华为发短信')
        }  
    }

    // 华为路由器工厂
    class HuaweiRouter extends RouterProduct{
        start(){
            console.log('开启华为路由器')
        }
        shutdown(){
            console.log('关闭华为路由器')
        }
        openwifi(){
            console.log('打开华为路由器')
        }
        setting(){
            console.log('设置华为路由器')
        }  
    }

    // 抽象产品工厂
    class IproductFactory{
        iphoneProduct(){}
        routerProduct(){}
    }

    // 小米产品工厂
    class XiaomiFactory extends IproductFactory{
        iphoneProduct(){
            return new XiaomiPhone()
        }
        routerProduct(){
            return new XiaomiRouter()
        }
    }

    // 华为产品工厂
    class HuaweiFactory extends IproductFactory{
        iphoneProduct(){
            return new HuaweiPhone()
        }
        routerProduct(){
            return new HuaweiRouter()
        }
    }

    const xiaomiF=new XiaomiFactory()
    const xiaomi = xiaomiF.iphoneProduct()

    xiaomi.start()
    xiaomi.shutdown()
    xiaomi.callup()
    xiaomi.sendSMS()

常见的例子,我们的弹窗,message,对外提供的api,都是调用api,然后新建一个弹窗或者Message的实例,就是典型的工厂模式

const Notification = function (options) {
    if (Vue.prototype.$isServer) return;
    options = options || {};
    const userOnClose = options.onClose;
    const id = 'notification_' + seed++;
    const position = options.position || 'top-right';
    options.onClose = function () {
        Notification.close(id, userOnClose);
    };
    instance = new NotificationConstructor({ data: options });
    if (isVNode(options.message)) {
        instance.$slots.default = [options.message];
        options.message = 'REPLACED_BY_VNODE';
    }
    instance.id = id;
    instance.$mount();
    document.body.appendChild(instance.$el);
    instance.visible = true;
    instance.dom = instance.$el;
    instance.dom.style.zIndex = PopupManager.nextZIndex();
    let verticalOffset = options.offset || 0;
    instances.filter(item => item.position === position).forEach(item => {
     verticalOffset += item.$el.offsetHeight + 16;
    });
    verticalOffset += 16;
    instance.verticalOffset = verticalOffset;
    instances.push(instance);
    return instance;
}

参考链接

3. 单例模式Singleton

单例就是保证一个类只有一个实例
实现的方法是,先判断实例是否存在,如果存在则直接返回,若不存在,则创建实例对象,并将实例对象保存在静态变量中,当下次请求时,则可以直接返回这个对象实例,这就确保了一个类只有一个实例对象。

应用:弹窗、登录、node模块、webpack模块(引用多次都是出来的都是一样)

模板:

class Analyzer{
	private static instance: Analyzer 
	static getInstance(){
		if(!Analyzer.instance){ // 如果Analyzer没有instance
			Analyzer.instance = new Analyzer()
		}
		return Analyzer.instance
	}
}

// 使用
const analyzer = Analyzer.instance()

require:

const mode1=require('mod')
const mode2=require('mod')
console.log(mod1==mod2) // true  引用同一个文件,出来的都是一样

引申:node全局方法:①global ②定义一个空模块作为全局变量,其他文件可以引入该模块修改增加

弹窗:

// 优化:单一职责,所有函数,它的职责只有一个
function creatDom() {
    let dom = document.createElement('div')
    dom.style.display='none'
    document.body.append(dom)
    return dom
}

let Single =(function() {
    let instance 
    return function(fn) {
        return  instance || (instance = fn.apply(this, arguments))
    }
})()

let Alert = (function(){
    let instance
    let dom 
    /* 违背单一职责
    function creatDom() {
        if(!dom){
            dom = document.createElement('div')
            dom.style.display='none'
            document.body.append(dom)
        }    
    }
    */
    function Alert(content) {
        instance = instance || (this instanceof Alert? this : new Alert(content)) 
        instance.init(content)  
        return instance
    }
    Alert.prototype.init=function(content){
        // creatDom()
        dom = Single(creatDom) // 创建一个新的单体
        dom.innerHTML = content
        dom.style.display = 'block'
    }
    Alert.prototype.hide = function(){
        dom.style.display = 'none'
    }
    return Alert
})()

let a=Alert('aaaaa')
let b=new Alert('bbbbb')
console.log(a.content)
console.log(b.content)
console.log(a === b)

var Module = (function () {
	function a(obj){  }
	function b(obj){  }
	function init(){
		a(abc);
		b(abc);
	}
	return {
        init: init
	};
}();
Module.init();
4. 建造者模式Builder (各执其职,拆解流程)

称为Builder模式、建造者模式、构建者模式、生成器模式
指挥者分配任务,建造者进行开发,各司其职,稳定在一个大的流程里面去,分步骤构建

// 首先创建一个员工的缓存对象,缓存对象需要修饰(添加属性和方法),然后向缓存对象添加姓名、年龄、职位
class EmployeeBuilder{
    constructor(){
        this.id = 1;
        this.firstname = "first";
        this.lastname = "last";
        this.age = 26;
        this.work = "work"
    }
    WithFirstName( firstname) {
        this.firstname = firstname;
        return this;
    }
    WithLastName( lastname) {
        this.lastname = lastname;
        return this;
    }
    WithAge( age) { 
        this.age= age;
        return this;
    }
    WithWork(work) { // 职位
        this.work = work;
        return this;
    }
}
const  emp1 = new EmployeeBuilder().WithFirstName("xiao").WithWork('工程师')
const  emp2 = new EmployeeBuilder().WithFirstName("Kenneth").WithAge(28)
console.log(emp1)
5. 原型模式prototype

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),
可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式(重新创建一个对象开销比较大,不合适,通过原型模式,帮你在原有对象克隆或拷贝出)

Object.create是原型模式的一种实现

const obj={
    name:'xiaoming',
    getName(){
        console.log(this.name)
    }
} 
let a=Object.create(obj)
a.name='lili'
a.getName() // lili

结构型

1. 适配器Adapter (接口适配、兼容内容、sub)

若将一个类的接口转换为客户希望的另外一个接口,可以使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器是一个既不得罪他人,也能达到自己目的的一个好方法。

场景:不同版本的电源插头

// target.js
module.exports = function(){
  this.request = function(){//原接口
    console.log('Target::request');
  }
}

// adapter.js
var util = require('util');
var Target = require('./target.js');
var Adaptee = require('./adaptee.js');
function Adapter(){
  Target.call(this);
  this.request = function(){//重写原接口
    var adapteeObj = new Adaptee();//重写的内容
    adapteeObj.specialRequest();
  }
}
util.inherits(Adapter, Target);//通过继承原模块, 获得原接口
module.exports = Adapter;
2. 桥接模式Bridge

将抽象部分和实现部分分离,使他们都可以独立地变化(又称柄体、接口模式)
在业务中出现比较多

优点:

  • 分离了抽象和实现部分,将实现层(DOM 元素事件触发并执行具体修改逻辑)和抽象层( 元素外观、尺寸部分的修改函数)解耦,有利于分层
  • 提高了可扩展性,多个维度的部件自由组合,避免了类继承带来的强耦合关系
  • 使用者不用关心细节的实现

缺点:桥接模式要求两个部件没有耦合关系,否则无法独立地变化;增加了系统复杂度

场景:

  • 产品的部件有独立的变化维度
  • 不希望使用继承来创建对象
  • 品部件的粒度越细,部件复用的必要性越大
    在这里插入图片描述
class Computer {
    constructor(brand){
        this.brand = brand
    }
}  

class Desktop extends Computer{
    constructor(props){
        super(props)
    }
    showType(){
        console.log(this.brand + '台式机')
    }
}

class Laptop extends Computer{
    constructor(props){
        super(props)
    }
    showInfo(){
        console.log(this.brand + '笔记本')
    }
}

const test = new Laptop('苹果');
test.showInfo()  // 苹果笔记本
3. 过滤器模式Filter、Criteria

过滤器(标准模式)也是一种结构型模式,允许开发者使用不同的标准/规则来过滤某一组对象

// 过滤出女性的数组
class Person{
    constructor(age, name, gender){
        this.age = age
        this.name  = name 
        this.gender  = gender

    }
}
class FemaleFilter{
    filter(arr){
        return arr.filter(function(item) {
            return item.gender == 'Female'
        })
    }
}

const people = new Array();
people.push(new Person(18, "yitian", 'Female'));
people.push(new Person(23, "zhang3", 'male'));
people.push(new Person(5, "li4", 'Female'));
people.push(new Person(26, "wang5", 'male'));
people.push(new Person(10, "zhao6", 'Female'));
const femaleFilter = new FemaleFilter();
const women = femaleFilter.filter(people);
console.log(people)
console.log(women)
4. 组合模式Composite (生成树形结构,表示“整体-部分”关系)

又叫整体-部分模式,允许你将对象组合成树形结构来表现整体-部分层次结构,让使用者可以以一致的方式处理组合对象以及部分对象

组合模式定义的包含组合对象和叶对象的层次结构,叶对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地组合下去。
让客户可以一致地使用组合结构的各节点,这就是所谓面向接口编程,从而减少耦合,便于扩展和维护
在这里插入图片描述

优点:

  • 组合对象和叶对象具有同样的接口,面向接口编程;
  • 想在组合模式的树中增加一个节点比较容易,在目标组合对象中添加即可,不会影响到其他对象,对扩展友好,符合开闭原则,利于维护;

缺点:增加了系统复杂度,树中对象不多则不用;创建了太多的对象,对象可能会让系统负担不起

场景:

  • 如果对象组织呈树形结构就可以考虑使用组合模式,特别是如果操作树中对象的方法比较类似时;
  • 使用者希望统一处理树形结构中的对象,不想写一堆if-else来处理树中的节点时,可以使用

1、虚拟dom中的vnode是这种形式,但数据类型简单

<div id="div1" class="container">
	<p>123</p>
	<p>456</p>
</div>

转为vnode
{
	tag:'div',
	atte:{
		id:'div1',
		class:'container'
	},
	children:[
		{tag:'p',attr:{},children:['123']},
		{tag:'p',attr:{},children:['456']}
	]
}

vue、react中创建节点生成dom树

// Vue
createElement('h3', { class: 'main-title' }, [
    createElement('img', { class: 'avatar', attrs: { src: '../avatar.jpg' } }),
    createElement('p', { class: 'user-desc' }, '长得帅老的快,长得丑活得久')
])

// React
React.createElement('h3', { className: 'user-info' },
  React.createElement('img', { src: '../avatar.jpg', className: 'avatar' }),
  React.createElement('p', { className: 'user-desc' }, '长得帅老的快,长得丑活得久')
)
5. 装饰器模式Decorator(扩展内容、加强功能)

装饰模式可以通过继承的方式,为一个基类对象扩展功能。为他人做嫁衣。

// Base.js
module.exports = function(){
  this.dosomething = function(){
   console.log("Nice to meet u.");
  }
}

// Decorator.js
var util = require("util);
var Base = require('./Base');
function Decorator(){
  Base.call(this);
  this.dosomething = function(){
     Base.dosomething();
     console.log('I am a decorator');//拓展内容
  }
}
util.inherits(Decorator, Base);//继承
module.exports = Decorator;
6. 外观模式Facade (封装兼容问题,简化调用)

又叫门面模式,定义一个将子系统的一组接口集成在一起的高层接口,以提供一个一致的外观。
让外界减少与子系统内多个模块的直接交互,从而减少耦合,让外界可以更轻松地使用子系统。

白话:为子系统中提供了一个高层接口,使得这一子系统更加容易使用

场景:

  • 抹平浏览器兼容性问题:里面有各种兼容方法
  • 工具类函数参数重载:对非可传参数做处理
  • umd提供一个前后端跨平台的解决方案(支持AMD、CommonJS、es6Module模块方式使用)
  • Axios可以使用在不同环境(js、node环境):判断环境,再使用对应的http模块

1、函数参数重载:

//  params为可选参数,可传可不传
function request(url, params, callback) => {
    if (isFunction(params)) {
        callback = params
        params = undefined
    }
}
request('xxx', {name:'xiaoming'}, fn)
request('xxx', fn)

2、Axios 不同环境请求不同的http模块:
nodejs环境中使用node的http模块来发送请求
浏览器环境中使用XMLHTTPRequest发送请求

function getDefaultAdapter() {
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // Nodejs 中使用 HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // 浏览器使用 XHR adapter
    adapter = require('./adapters/xhr');
  }
}

3、浏览器兼容问题
涉及到兼容性,参数支持多格式,有很多这种代码,对外暴露统一的api,比如上面的 o n 支 持 数 组 , on 支持数组, onoff参数支持多个情况, 对面只用一个函数,内部判断实现自己封装组件库 经常看到

myEvent = {
    stop: function (e) {
        if (typeof e.preventDefault() === "function") {
            e.preventDefault();
        }
        if (typeof e.stopPropagation() === "function") {
            e.stopPropagation();
        }
        //for IE 
        if (typeof e.returnValue === "boolean") {
            e.returnValue = false;
        }
        if (typeof e.cancelBubble === "boolean") {
            e.cancelBubble = true;
        }
    },
    addEvent(dom, type, fn) {
        if (dom.addEventListener) {
            dom.addEventListener(type, fn, false);
        } else if (dom.attachEvent) {
            dom.attachEvent('on' + type, fn);
        } else {
            dom['on' + type] = fn;
        }
    }
}
7. 享元模式Flyweight (共享内存、优化性能)

享元模式 (Flyweight Pattern)运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。

享元模式的主要思想是共享细粒度对象,也就是说如果系统中存在多个相同的对象,那么只需共享一份就可以了,不必每个都去实例化每一个对象,这样来精简内存资源,提升性能和效率。

优点:减少系统中的对象数量,提高了程序运行效率和性能,精简了内存占用,加快运行速度;
外部状态相对独立,不会影响到内部状态,所以享元对象能够在不同的环境被共享;

缺点: 引入了共享对象,使对象结构变得复杂;共享对象的创建、销毁等需要维护,带来额外的复杂度

场景:

  • 弹窗:频繁的dom创建销毁操作,就可以引入对象池来节约一些dom创建损耗(弹窗只创建一份div元素,后面的只需要更换innerHTML内容)
  • 事件代理
<!-- 无线下拉列表,将事件代理到高层节点上。如果都绑定到a标签上,对内存开销太大-->
<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <a href="#">a5</a>
    <!--无限下来列表-->
</div>
<script>
    // 也属于代理模式
    var div1 = document.getElementById('div1')
    div1.addEventListener('click',function (e) {
        var target = e.target
        if(e.target.nodeName === 'A'){
            console.log(target.innerHTML)
        }
    })

例子2:
vue消息组件:1. 弹窗逻辑一样 2. 四中弹窗,颜色,icon不同 3. 接收文案
交互方式——弹出、隐藏,由共享对象所拥有
提示icon、背景样式、字体样式提供接口可配置

//Message.js 伪代码
export default {
    install(Vue) {
        // 在使用插件Vue.use(Message)时实例化一个Dialog组件对象 
        const Dialog = new Vue({
            data() {
                return { 
                    icon: '', 
                    fontStyle: '',
                    backgroundStyle: '', 
                    text: '' 
                }
            }
            ... 
            })
        // 扩展Vue的`prototype` 
        Vue.prototype.$Message = {
            success(text) {
                // 改变Dialog的data.xx的值触发Dialog的更新 
                Dialog.icon = successIcon
                Dialog.fontStyle = successFontStyle
                Dialog.backgroundStyle = successBackgroundStyle
                Dialog.text = text
                // 获取Dialog的最新DOM添加到body标签中 
                document.body.appendChild(Dialog.$el)
            },
            warning(text) {
                // 同上 ... 
                document.body.appendChild(Dialog.$el)
            },
            error(text) {
                // 同上 ... 
                document.body.appendChild(Dialog.$el)
            }
        }
    }
}

Dialog只会在项目初始化时被 new 一次,每次使用Message组件通过改变Dialog的状态获取组件DOM,其实很容易知道new一个组件的成本要比一个组件的更新成本高很多

8. 代理模式Proxy (控制访问)

定义:在不改变原始类(代理类)代码的情况下,通过引入代理类来给原始类附加功能
白话:使用者无权访问目标对象,通过代理做授权和控制。顾名思义就是帮别人做事,为其他对象提供一种代理以控制对这个对象的访问

优点:

  • 起到保护目标对象的作用;降低了系统的耦合度,通过修改代理
  • 扩展目标对象的功能;
    代理模式能将访问者与目标对象分离,在一定程度上,如果我们希望适度扩展目标对象的一些功能,通过修改代理对象就可以了,符合开闭原则;
    代理模式的缺点主要是增加了系统的复杂度,要斟酌当前场景是不是真的需要引入代理模式(十八线明星就别请经纪人了)。

场景:

  • 科学上网
  • webpack proxy解决跨域、nginx反向代理
  • Axios使用拦截器 interceptor 可以提前对 request 请求和 response 返回进行一些预处理
  • vue3.0使用proxy实现数据监听修改
  • 缓存代理:备忘模式就是使用缓存代理的思想,将复杂计算的结果缓存起来,下次传参一致时直接返回之前缓存的计算结果

在这里插入图片描述

行为型

1. 职责链模式Chain of Responsibility

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
作用:将发起者和各个处理者进行隔离
应用场景:js、jquery、promise.then的链式操作

class Action{
    constructor(name){
        this.name = name
        this.nextAction = null
    }
    setNextAction(action){
        this.nextAction = action
    }
    handle(){
        console.log(`${this.name}审批`)
        if(this.nextAction != null){
            this.nextAction.handle()
        }
    }
}

let a1= new Action('组长')
let a2= new Action('经理')
let a3= new Action('总监')

a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle() // 组长审批 经理审批 总监审批
2. 命令模式command

执行命令时,发布者和执行者分开,中间加入命令对象,作为中转站
应用:网页富文本编辑器操作,浏览器封装了一个命令对象
document.execCommand(‘bold’)
document.execCommand(‘undo’)
在这里插入图片描述

class Receiver{
    exec(){
        console.log('执行')
    }
}

class Command{
    constructor(receiver){
        this.receiver = receiver
    }
    cmd(){
        console.log('触发命令')
        this.receiver.exec()
    }
}

class Invoker{
    constructor(command){
        this.command = command
    }
    invoke(){
        console.log('开始')
        this.command.cmd()
    }
}

// 士兵
let soldier = new Receiver()

// 小号手
let trumpeter = new Command(soldier)

// 将军
let general = new Invoker(trumpeter)
general.invoke()
3. 迭代模式Iterator(按序访问每个元素)

迭代器提供一种方法访问一个对象中各个元素而又不暴露该对象的内部细节。
能顺序访问一个集合,使用者无需知道集合的内部结构
场景:es6 Iterator、each、filter、reduce、map

// 模拟 Iterator
class Iterator{
    constructor(container){
        this.list = container.list
        this.index = 0
    }
    next(){
        if(this.hasNext()){
            return this.list[this.index++] //如果还有就不断累加index
        }
        return null // 如果没有下一项就return null
    }
    hasNext(){
        if(this.index >= this.list.length){
            return false
        }
        return true
    }
}

class Container{
    constructor(list){
        this.list = list
    }
    // 生成遍历器
    getIterator(){
        return new Iterator(this)
    }
}

let arr=[1,2,3,4,5,6]
let container = new Container(arr) // 里面有生成迭代器的方法
let iterator = container.getIterator() // 生成遍历器 不用for来遍历
while (iterator.hasNext()) {
    console.log(iterator.next())
}
console.dir(iterator)
// es6 Iterator模拟each
function  each(data) {
    // 生成遍历器
    let iterator = data[Symbol.iterator]() //这个函数执行就会返回遍历器

    // console.log(iterator.next()) // 有数据时返回 {value:1 , done:false}
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next()) // 没数据时返回 {value:undefined, done:true}
    
    let item ={done:false}
    while (!item.done) {
        item = iterator.next()
        if(!item.done){
            console.log(item.value)
        }
    }
}

let arr=[1,2,3,4]
let nodeList=document.getElementsByTagName('p')
let m=new Map() // 一个有序对象
m.set('a',100)
m.set('b',100)

each(arr)
each(nodeList)
each(m)

模拟map、reduce、filter,底层就是获取每个元素,放到回调函数里执行

// 模拟map
_.map = function(obj, func, context){ 
    let results=[]
    for(var i=0;i<obj.length;i++){
        results[i] = func(obj[i], i, obj)
    }
    return results
}

// 模拟reduce
_.reduce = function(obj, func, memo, init){
    for(var i=0;i<obj.length;i++){
        // memo就是传过来的第三个参数 0
        // console.log(memo) // 0 1 3 6 10 15 21
        memo = func(memo, obj[i], i, obj)   //  func是迭代器,,对迭代元素进行处理
    }
    return memo
}

// 模拟filter
// 判断是否成立,成立就push到数组里面去
_.filter = function(obj, func, context){ 
    let results=[]
    for(var i=0;i<obj.length;i++){
        if(func(obj[i])){
            results.push(obj[i])
        }
    }
    return results
}

// 测试:
console.log(_.map([1,2,3], function(value, index, object){
    return value*3
}, obj))
console.log(_.reduce([1,2,3,4,5,6],function(memo, value, index, obj){
    return memo + value
},0))
4. 中介者模式Mediator

通过一个中介者对象,其他所有的相关对象都通过该中介者对象来通信,而不是相互引用,当其中的一个对象发生改变时,只需要通知中介者对象即可。通过中介者模式可以解除对象与对象之间的紧耦合关系。

场景:例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。

redux,vuex 都属于中介者模式的实际应用,我们把共享的数据,抽离成一个单独的store, 每个都通过store这个中介来操作对象

目的就是减少耦合
在这里插入图片描述

// A和B都不能相互访问,只能通过中介者

// 买房
class A{
    constructor(){
        this.number = 0
    }
    setNumer(num, m){
        this.number = num
        if(m){
            m.setB()
        }
    }
}

// 卖房
class B{
    constructor(){
        this.number = 0
    }
    setNumer(num, m){
        this.number = num
        if(m){
            m.setA()
        }
    }
}

// 中介者
class Mediator{
    constructor(a, b){
        this.a = a
        this.b = b
    }
    setB(){
        let number = this.a.number
        this.b.setNumer(number*100)
    }
    setA(){
        let number = this.b.number
        this.a.setNumer(number/100)
    }
}


// 测试
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumer(100, m) // 通知中介者帮你干活
console.log(a.number, b.number)
b.setNumer(100, m) // 通知中介者帮你干活
console.log(a.number, b.number)

5. 备忘录模式Memento

随时记录一个对象的状态变化,随时可以恢复到之前的某个状态

应用:react、redux、时间旅行的功能,富文本编辑器

// 备忘对象
class Memento{
    constructor(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
}

// 备忘列表
class CareTaker{
    constructor(){
        this.list = []
    }
    add(memento){
        this.list.push(memento)
    }
    get(index){
        return this.list[index]
    }
}

// 编辑器
class Editor{
    constructor(){
        this.content = null
    }
    setContent(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
    saveContentToMemento(){
        return new Memento(this.content)
    }
    getContentFromMemento(memento){
        this.content = memento.getContent()
    }
}

// 测试代码
let editor = new Editor()
let careTaker = new CareTaker()

editor.setContent('1111')
editor.setContent('2222')
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份

editor.setContent('3333')
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份

editor.setContent('4444')

console.log(editor.getContent()) // 4444
// console.log(editor) // Editor {content: "4444"}
// console.log(careTaker) // [{content: "2222"},{content: "3333"}]
editor.getContentFromMemento(careTaker.get(1)) // 撤销
console.log(editor.getContent()) // 3333
editor.getContentFromMemento(careTaker.get(0)) // 撤销
console.log(editor.getContent()) // 2222

参考

6. 观察者模式Observer

它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个目标对象,当目标对象发生改变时,所有依赖于它的对象都将得到通知(一个目标对应多个观察者)

优点:
①实现了观察者和目标之间的抽象耦合。
②实现了动态联动。由于观察者模式对观察者的注册实行管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。

缺点:

  • 每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法
  • 目标无法给单独的观察者发秘密消息,目标和观察者存在依赖关系

应用场景:网页事件绑定(addEvnetListener)、事件委托、Promise、jQuery callbacks、聊天室程序(群发消息)、网络游戏(多人联机)

例子一:
在这里插入图片描述

// 主题,保存状态,状态变化之后触发所有观察者对象
class Subject{
    constructor(){
        this.state = 0
        this.subs= [] // 存储观察者
    }
    mew(state){
        this.state = state
        this.notifyAllObservers()
    }
    notifyAllObservers(){
        this.subs.forEach(observer=>{
            observer.update()
        })
    }
    attach(observer){  // 注册
        this.subs.push(observer)
    }
}

// 观察者
class Observer{
    constructor(name, subject){
        this.name = name
        this.subject = subject
        this.subject.attach(this) // 注册进观察者
    }
    update(){
        console.log(`${this.name}=====>${this.subject.state}`)
    }
}

let cat=new Subject()
let mouse=new Observer('mouse', cat)
let person=new Observer('person', cat)

/* 猫的大脑里有整个观察者的清单,哪里有老鼠,主人在哪里,
当他发生猫叫的时候,会去遍历清单(所有观察者,调用他们的方法)
*/
cat.mew('喵喵喵喵喵~~~')
cat.mew('哈哈哈哈~~~')
cat.mew('嘿嘿嘿~~~')

例子:jQuery callbacks

<div>
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <a href="#">a5</a>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
// 自定义事件,自定义回调
var callbacks = $.Callbacks()
callbacks.add(function(info) { // 添加观察者回调事件
    console.log('fn1', info)
})
callbacks.add(function(info) {
    console.log('fn2', info)
})
callbacks.add(function(info) { 
    console.log('fn3', info)
})
callbacks.fire('gogogo') // 触发
发布订阅模式(观察者模式的2.0版本)

发布订阅模式是观察者模式的提升和改进,观察者模式的升级版

订阅者(观察者)把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。

思想:内部保存了一个对象存储订阅的函数,调用者通过名字来触发函数,订阅多个就按照队列的形式触发

场景:nodejs自定义事件(EventEmitter)

// 调度中心
class Public{
    constructor(){
        this.handlers = {}
    }
    on(eventType , handler){ // 订阅事件
        if (!(eventType in this.handlers)) {
            this.handlers[eventType] = [];
        }
        this.handlers[eventType].push(handler)
        return this
    }
    emit(eventType){ // 发布事件
        var handlerArgs = Array.prototype.slice.call(arguments, 1);
        var length = this.handlers[eventType].length
        for (var i = 0; i < length; i++) {
            this.handlers[eventType][i].apply(this, handlerArgs);
        }
        return this;
    }
    off(eventType , handler){ // 删除订阅事件
        var currentEvent = this.handlers[eventType];
        var len = 0;
        if (currentEvent) {
            len = currentEvent.length;
            for (var i = len - 1; i >= 0; i--) {
                if (currentEvent[i] === handler) {
                    currentEvent.splice(i, 1);
                }
            }
        }
        return this ;
    }
}

// 订阅者
function ObserverA(data){
    console.log('a subscribe:' + data)
}
function ObserverB(data){
    console.log('b subscribe:' + data)
}
function ObserverC(data){
    console.log('c subscribe:' + data)
}

var publisher = new Public();
//订阅事件
publisher.on('a', ObserverA); // 给a订阅观察者ObserverA、ObserverC
publisher.on('b', ObserverB); // 给b订阅观察者ObserverB
publisher.on('a', ObserverC);

//触发事件
publisher.emit('a', 'a第1次时间发布');
publisher.emit('b', 'b第1次时间发布');
publisher.emit('a', 'a第2次时间发布'); 

//删除订阅事件
publisher.off('a', ObserverA);

// 再次触发
publisher.emit('a', 'a第3次时间发布');
publisher.emit('b', 'b第2次时间发布');

nodejs自定义事件

var EventEmitter=require('events').EventEmitter;
var emitter1=new EventEmitter()
// 监听事件
emitter1.on('error',err=>{
  console.log("发生错误")
})
// 触发事件
emitter1.emit('error', err) 
发布订阅和观察者的区别?
  • 观察者:一个或多个观察者对目标的状态感兴趣,每次目标状态发生会广播通信,调用每个观察者的更新方法;目标无法给单独的观察者发秘密消息。
  • 发布订阅:在目标和观察者之间增加一个调度中心/事件通道,事件通道会把发布者订阅者关联起来,所以他们之间没有直接交流,互相独立运行,互不干扰;目标可以秘密发消息给某个订阅者。
    说白了,就是多了个事件通道
7. 状态模式State

状态模式:对象行为是基于状态来改变的。
内部的状态转化,导致了行为表现形式不同。所以,用户在外面看起来,好像是修改了行为。

状态模式是状态机的一种实现方法。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。

优点

  • 封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。
  • 每个状态都是确定的,所以对象行为是可控的。

缺点

  • 状态模式的关键是将事物的状态都封装成单独的类,这个类的各种方法就是“此种状态对应的表现行为”。因此,状态类会增加程序开销。

FSM(有限状态机)里面有3种状态:download、pause、deleted。 控制状态转化的代码也在其中。
DownLoad类就是,常说的Context对象,它的行为会随着状态的改变而改变。

const FSM = (() => {
  let currenState = "download";
  return {
    download: {
      click: () => {
        console.log("暂停下载");
        currenState = "pause";
      },
      del: () => {
        console.log("先暂停, 再删除");
      }
    },
    pause: {
      click: () => {
        console.log("继续下载");
        currenState = "download";
      },
      del: () => {
        console.log("删除任务");
        currenState = "deleted";
      }
    },
    deleted: {
      click: () => {
        console.log("任务已删除, 请重新开始");
      },
      del: () => {
        console.log("任务已删除");
      }
    },
    getState: () => currenState
  };
})();

class Download {
  constructor(fsm) {
    this.fsm = fsm;
  }

  handleClick() {
    const { fsm } = this;
    fsm[fsm.getState()].click();
  }

  hanldeDel() {
    const { fsm } = this;
    fsm[fsm.getState()].del();
  }
}

// 开始下载
let download = new Download(FSM);

download.handleClick(); // 暂停下载
download.handleClick(); // 继续下载
download.hanldeDel(); // 先暂停, 再删除
download.handleClick(); // 暂停下载
download.hanldeDel(); // 删除任务

红绿灯

// 状态(红、绿、黄灯)
class State{
    constructor(color){
        this.color=color
    }
    handle(context){
        console.log(`turn to ${this.color} light`)
        // 设置状态
        context.setState(this) 
    }
}

// 主体
class Context{
    constructor(){
        this.state=null
    }
    getState(){
        return this.state
    }
    setState(state){
        this.state = state
    }
}

let context = new Context()

let green = new State('green')
let yellow = new State('yellow')
let red = new State('red')

// 绿灯亮了
green.handle(context)
console.log(context.getState())
// 绿灯亮了
yellow.handle(context)
console.log(context.getState())
// 绿灯亮了
red.handle(context)
console.log(context.getState())

有限状态机制:插件 javascript-state-machine

<button id="btn1">btn</button>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/javascript-state-machine/2.0.0/state-machine.min.js"></script>
<script>
// 初始化状态机模型
let fsm = StateMachine.create({
    // initial:'收藏',
    initial: { state: '收藏', event: 'init' },
    events:[
        {
            name: 'doStore',
            from: '收藏',
            to: '取消收藏'
        },
        {
            name: 'deleteStore',
            from: '取消收藏',
            to: '收藏'
        }
    ],
    callbacks:{
        // 监听执行收藏
        doStore:function(){
            console.log('收藏成功') // 可以 post 请求
            this.current = '取消收藏'
            updateText()
        },
        // 监听取消收藏
        deleteStore:function(){
            console.log('已经取消收藏成功') // 可以 post 请求 
            this.current = '收藏'
            updateText()
        }
    }
})

let $btn=$('#btn1')

$btn.click(function() {
    if(fsm.is('收藏')){
        fsm.doStore()
    }else{
        fsm.deleteStore()
    }
    
})

// 更新按钮的文案
function  updateText() {
    $btn.text(fsm.current)
}

// 初始化文案
updateText()

8. 空对象模式Null Object

构建一个用于响应无数据或者默认处理的对象
比如查询数据库中有没有某个人名,结果找不到输出了一段文字,而不是直接输入null

9. 策略模式strategy

不同策略分开处理
避免出现大量if…else或者switch…case

// 正常写法 
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
    if (registerForm.userName.value === '') {
        alert('用户名不能为空');
        return false;
    }
    if (registerForm.password.value.length < 6) {
        alert('密码长度不能少于 6 位');
        return false;
    } if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
        alert('手机号码格式不正确');
        return false;
    }
}

// 使用
var strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    isMobile: function (value, errorMsg) {
        // 手机号码格式
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    }
};
10. 模板方法 template method

比如说可以对自己内部的顺序处理,把特殊顺序、逻辑、结构的方法给做成封装和合并,给它输出对外统一的方法
作用:减低组件和组件之间,模块化与模块化之间的耦合度

应用场景:vue的solt react的children

// react children
class Parent {
    constructor() { }
    render() {
        <div>
            <div name="tom"></div>
            //  算法过程:children要渲染在name为joe的div中
            <div name="joe">{this.props.children}</div>
        </div>
    }
}

class Stage {
    constructor() { }
    render() {
        // 在parent中已经设定了children的渲染位置算法 
        <Parent>
            // children的具体实现
            <div>child</div>
        </Parent>
    }
}
// vue slot
<template>
    <div>
        <div name="tom"></div>
        <div name="joe">
            <!--vue中的插槽渲染children-->
            <slot />
        </div>
    </div>
</template>

<template>
    <div>
        <parent>
        <!-- children的具体实现 -->
            <div>child</div>
        </parent>
    </div>
</template>
11. 解释器模式

描述语言与法如何定义,如何解释和编译
应用:babel解析es6语法 、sass\less解析css样式

12. 访问者模式Visitor

将数据操作和数据结构进行分离

区别

代理、桥接、装饰器、适配器的区别?

  • 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是
    控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
  • 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相
    对独立地加以改变。
  • 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持
    多个装饰器的嵌套使用。
  • 适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理
    模式、装饰器模式提供的都是跟原始类相同的接口。

适配器:提供不同的接口
代理模式:提供一模一样的接口;显示原有功能,但是经过限制或者阉割之后的(经纪人限制你访问明星电话,只可以获取他的)
装饰器:扩展功能,原有功能不变且可直接使用

工厂模式、建造者模式的区别?

  • 工厂:创建的都是对象实例或者类簇。不会关心构建过程,只关心什么产品由什么工厂生产即可
  • 建造者:目的是为了创建复杂对象,它更关系的是创建这个对象的整个过程和细节。比如创建一个人,不仅仅要得到人的实例,还要关注人应该穿什么衣服,是男是女,兴趣爱好。

经典例子:
顾客走进一家餐厅点餐,我们利用工厂模式,根据用户不同选择来制作食物,比如披萨、汉堡、沙拉。
对于披萨来说,可以通过建造者模式来根据用户选择的不同配料(奶酪、培根、榴莲等)来制作披萨。

单例、工厂、建造者、原型模式创建对象之间的区别?

  • 单例:用来创建全局唯一的对象
  • 工厂:用来创建不同但是相关类型的对象(继承同一父类的对象),由给定参数来决定创建哪种类型的对象
  • 建造者:用来创建复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象
  • 原型:针对创建成本较大的对象,利用已有对象进行复制的方式创建,达到节省创建时间的目的

代理、适配器、装饰器的区别?

  • 适配器:解决接口之间不匹配的问题,通常是为所适配的对象提供一个不同的接口
  • 代理:主要目的是控制其他访问者对目标对象的访问
  • 装饰器:给目标对象添加功能,也就是动态地添加功能

外观、中介者模式的区别?

  • 外观: 封装使用者对子系统内模块的交互,方便使用者对子系统的调用;
  • 中介者: 封装子系统间各模块之间的接交互,松散模块间的耦合

7.1 享元模式和工厂模式、单例模式
在区分出不同种类的外部状态后,创建新对象时需要选择不同种类的共享对象,这时就可以使用工厂模式来提供共享对象,在共享对象的维护上,经常会采用单例模式来提供单实例的共享对象。

7.2 享元模式和组合模式
在使用工厂模式来提供共享对象时,比如某些时候共享对象中的某些状态就是对象不需要的,可以引入组合模式来提升自定义共享对象的自由度,对共享对象的组成部分进一步归类、分层,来实现更复杂的多层次对象结构,当然系统也会更难维护。

7.3 享元模式和策略模式
策略模式中的策略属于一系列功能单一、细粒度的细粒度对象,可以作为目标对象来考虑引入享元模式进行优化,但是前提是这些策略是会被频繁使用的,如果不经常使用,就没有必要了。

组合、职责链模式的区别?

组合模式是天生实现了职责链模式的

  • 组合: 请求在组合对象上传递,被深度遍历到组合对象的所有子孙叶节点具体执行;(对象上传递,所有子孙节点执行)
  • 职责链: 实现请求的发送者和接受者之间的解耦,把多个接受者组合起来形成职责链,请求在链上传递,直到有接受者处理请求为止;(链上传递,接收者执行)

7.1 桥接模式和策略模式
桥接模式: 复用部件类,不同部件的实例相互之间无法替换,但是相同部件的实例一般可以替换;
策略模式: 复用策略类,不同策略之间地位平等,可以相互替换;
7.2 桥接模式与模板方法模式
桥接模式: 将组成产品的部件实例的创建,延迟到实例的具体创建过程中;
模版方法模式: 将创建产品的某一步骤,延迟到子类中实现;
7.3 桥接模式与抽象工厂模式
这两个模式可以组合使用,比如部件类实例的创建可以结合抽象工厂模式,因为部件类实例也属于一个产品类簇,明显属于抽象工厂模式的适用范围,如果创建的部件类不多,或者比较简单,也可以使用简单工厂模式。

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值