JavaScript设计模式

55 阅读6分钟

一、设计模式综述

1.1、设计模式定义

设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

简单直白:设计模式就是一种理论,通过一些设计思维来解决平时编写底层或业务代码时遇到的场景问题,比如早期业务中的一个封装类,同时带有一些封装方法。如果现在该类不能再满足全部业务场景,且不允许修改原方法,此时就需要装饰器或适配器模式来解决;又比如当设计一个场景,在调用一个固定对象时一定要先执行某些方法,比如验证登录、验证身份ID等场景,此时就应该用到代理模式。这种例子有很多,可以先看一下设计模式的分类。

1.2、模式分类

类型模式名称
创造型工厂 单例 原型
组合型(结构性)适配器 装饰器 代理 外观 桥接
行为型观察者 命令 中介者 状态 策略 接收器 迭代器 访问者 模板方法 职责链 备忘录

二、工厂模式

2.1、基本特征

有三种形式:简单工厂模式、工厂方法模式、抽象工厂模式。在JavaScript中最常见的是简单工厂模式。 工厂模式的设计思想:

  • 将new操作单独封装,只对外提供相应接口。
  • 遇到new时,就要考虑是否使用工厂模式

2.2、核心作用

工厂模式的核心作用如下:

  • 主要用于隐藏创建实例的复杂度,只需对外提供一个接口。
  • 实现构造函数与创建者的分离,满足开放封闭的原则。

2.3、分类

  • 简单工厂模式:一个工厂对象创建一种产品对象实例。即用来创建同一类对象
  • 工厂方法模式:建立抽象核心类,将创建实例的实例重心放在 _核心抽象大类的子类 _上
  • 抽象工厂模式:

2.4、实例演示

//定义产品
class Product(){
  constructor(name){
    this.name = nama
  }
  init(){
    console.log('初始化产品')
  }
}

//定义工厂
class Factory(){
  creat(name){
    return new Product(name) //核心思想
   }
}

let c = new Factory()
let p = c.creat('p1')
p.init()
//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;
        default:
          throw new Error('参数错误')
      }
  }
}

//调用
let superAdmin = User.getInstance('superAdmin')
let admin = User.getInstance('admin')

//独立User类
class User{
  constructor(name='',viewPage=[]){
    this.nama = name
    this.viewPage = viewPage
  }
}

//子类继承User类
class UserFactory extends User{
  constructor(name,viewPage){
    super(name,viewPage)
  }
  //编写子类的方法
  creat(role){
   switch(role){
     case 'superAdmin' :
       return new UserFactory({name:'超级管理员',viewPage:['首页', '通讯录', '发现页', '应用数据', '权限管理']});
       break;
     case 'admin' :
       return new UserFactory({name:'管理员',viewPage:['首页', '通讯录']});
       break;
     default :
       throw new Error('参数错误')
   }
  }
}

//调用
let userFactory = new UserFactory()
let superAdmin = userFactory.creat('superAdmin')
let admin = userFactory.creat('admin')
let user = userFactory.creat('user')//Uncaught Error: 参数错误

三、单例模式

3.1基本特征

单例模式,就是保证实例在全局的唯一性,如下:

  • 系统中唯一被使用
  • 一个类只能有一个实例(必须是强等于 ===)

在日常业务中,比如 最基本的弹窗、购物车等都是使用单例模式,因为无论是单页面应用还是多页面应用,需要的这些业务场景只会同时存在一个。

3.2实例演示

class Model {
  login(){
    console.log('login...')
  }
}

Model.creat = (function(){
  let instance
  return function(){
    if(!instance){
      instance = new Model()
    }
    return instance
  }
})()

let m1 = Model.creat()
let m2 = Model.creat()
console.log(m1 === m2)  //true

上述代码是一个简单的单例模式,通过JavaScript的立即执行函数和闭包,将初始值确定,之后客通过判断instance是否存在,存在返回。反之创建instance实例并返回,这样保证了一个类只有唯一的实例存在。

let Model = (function(){
  let instance
  return function(name){
    if(instance){
      return instance
    }
    this.name = name
    instance = this
  }
})()
Model.prototype.getName = function(name){
  return this.name
}

let q = new Model('问题')
let a = new Model('回答')
console.log(q === a)     //true
console.log(q.getName()) //问题
console.log(a.getName()) //问题 

单例模式的实现实质就是,创建一个可以返回对象实例的引用以及可以获取该实例的方法,保证实例的唯一性

四、适配器模式

适配器模式的作用:**解决两个接口/方法间的接口不兼容问题,**它并不需要考虑接口是如何实现,也不用考虑将来该如何修改;适配器不需要修改已有接口,就可以使他们协同工作;

var googleMap = {
  show:function(){
    console.log('Google Map')
  }
}
var baiduMap = {
  display:function(){
    console.log('Baidu Map')
  }
}

var renderMap = function(map){
  if(map.show instanceof Function){
    map.show()
  }
}
//是配偶器修改
var baiduMapAdapter = {
  show:function(){
    return baiduMap.display()
  }
}
renderMap(googleMap);         //Google Map
renderMap(baiduMapAdapter);   //Baidu Map
    我们作为前端开发人员,对页面上期待得到的数据和数据格式肯定是比较了解的,但是在前后端分离的开发模式中有的时候会遇到这种尴尬的处境:
    我们都知道很多UI组件或者工具库会按指定的数据格式进行渲染,但是这个时候后端是不知道的;所以可能接口出来的数据我们是不能直接正常的在页面上渲染的,而此时老板催促我们赶紧上线,而后端坚持认为数据格式没问题,坚决不修改;这个时候我们可以通过适配器模式来前端格式化数据;

后端返回的json格式数据

[
  {
    "day": "周一",
    "uv": 6300
  },
  {
    "day": "周二",
    "uv": 7100
  },  {
    "day": "周三",
    "uv": 4300
  },  {
    "day": "周四",
    "uv": 3300
  },  {
    "day": "周五",
    "uv": 8300
  },  {
    "day": "周六",
    "uv": 9300
  }, {
    "day": "周日",
    "uv": 11300
  }
]

["周二", "周二", "周三""周四""周五""周六""周日"] //x轴的数据

[6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据

使用适配器来解决

//x轴适配器
fucntion EchartXAdapter(res){
  return res.map( item => item.day)
}

//坐标点的适配器
function EchartDataAdapter(res){
  return res.map( item => item.uv)
}

五、代理模式

5.1定义及特征

定义

为一个对象提供一个代用品或者占位符,以便控制对它的访问

通俗来说,代理模式要突出“代理”的含义,该模式场景需要三类角色,分别为使用者、目标对象和代理者,使用者的目的是直接访问目标对象,但却不能直接访问,而是要先通过代理者。因此该模式非常像明星代理人的场景。其特征为:

  • 使用者无权访问目标对象
  • 中间加代理,通过代理作授权和控制

5.2实际应用

1、HTML元素代理

<body>
    <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', (e) => {
          var target = e.target;
          if(target.nodeName === 'A') {
             alert(target.innerHTML);
          }
       })
    </script>
</body>

该例中,我们并未直接在元素上定义点击事件,而是通过监听元素点击事件,并通过定位元素节点名称来代理到标签的点击,最终利用事件冒泡来实现相应的点击效果。

2、ES6 Proxy

let star = {
    name: '陈奕迅',
    song: '孤勇者'
    age: 40,
    phone: 13089898989
}
let agent = new Proxy(star , {
    get(target , key) {
        if(key == 'phone') {
            // 返回经济人自己的电话
            return 15667096303
        }
        if(key == 'price') {
           return 20000000000
        }
        return target[key]
    },
    set(target , key , val) {
       if(key === 'customPrice') {
          if(val < 100000000) {
              throw new Error('价格太低')
          }
          else {
              target[key] = value;
              return true
          }
       }
    }
})

// agent 对象会根据相应的代理规则,执行相应的操作:
agent.phone // 15667096303  
agent.price // 20000000000 

六、观察者模式

6.1定义及特征

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 观察者模式是一种对象行为型模式。

自己的理解

JavaScript本身就是基于事件驱动的,所以观察者模式还是比较容易理解。像流行的MVVM也离不开观察者模式,数据的变化通知视图更新。在轻文的一个需求演绘登录后在不刷新页面的情况下更新评论框,个人信息等状态。使用观察者模式在各个模块中通信。

观察者模式的UML类图

6.2实例

1、比如监听 div 的 click Event , div.addEventListener('click',functuin(e){ ... }) ,观察 div 对象,当div被点击了,执行匿名函数

2、Vue view-model 双向绑定,通过 Object.defineProperty 来实现对象的 ‘响应式’ , reactiveGetter 进行依赖收集,reactiveSetter 更新属性时,通知观察者更新最新状态

例子

 class Subject {
    constructor() {
      this.state = 0 
      this.observers = []
    }
    
    getState() {
      return this.state
    }
    
    setState(state) {
      this.state = state 
      this.notifyAllObservers()
    }
    
    notifyAllObservers() {
      this.observers.forEach(observer => {
        observer.update()
      })
    }
    
    attach(observer) {
      this.observers.push(observer)
    }
  }
  
  // Observer 
  class Observer {
    constructor(name , subject) {
      this.name = name 
      this.subject = subject 
      this.subject.attach(this)
    }
    
    update() {
      console.log('${this.name}' update , 
                   state : ${this.subject.getState()}')
    }
  }
  
  // demo 
  let subject = new Subject() 
  let observer_1 = new Observer('observer_1',subject)
  let observer_2 = new Observer('observer_2',subject)