JavaScript设计模式
什么是设计模式?
定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案,通俗的讲就是一套可以被复用的,编目分明的经验总结。 作用:代码可复用,提高代码的可维护性; 期望:当我们遇到合适的场景时,我们可能会条件反射一样自然而然想到符合这种场景的设计模式
有哪些设计模式?
主要分为以下三类:
创建型模式:解决创建类或者实例化对象时候,产生的问题 结构型模式:解决类或者对象组合在一起的时候,产生的问题 行为型模式:解决类或者对象之前耦合,职责关系的问题
具体设计模式有以下
创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 行为型模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问模式
JavaScript常用设计模式
单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。 适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次
class CreateUser {
constructor ( name ) {
this . name = name;
this . getName ( ) ;
}
getName ( ) {
return this . name;
}
}
var ProxyMode = ( function ( ) {
var instance = null ;
return function ( name ) {
if ( ! instance) {
instance = new CreateUser ( name) ;
}
return instance;
}
} ) ( ) ;
var a = new ProxyMode ( "aaa" ) ;
var b = new ProxyMode ( "bbb" ) ;
console. log ( a === b) ;
简单工厂模式(静态工厂模式)
定义: 由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象
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 ;
case 'user' :
return new User ( { name: '普通用户' , viewPage: [ '首页' , '通讯录' , '发现页' ] } ) ;
break ;
default :
throw new Error ( '参数错误, 可选参数:superAdmin、admin、user' )
}
}
}
let superAdmin = User. getInstance ( 'superAdmin' ) ;
let admin = User. getInstance ( 'admin' ) ;
let normalUser = User. getInstance ( 'user' ) ;
工厂模式(Factory Pattern)
定义:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 适用场景: 如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择 将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式; 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性
class Product {
constructor ( name ) {
this . name = name
}
init ( ) {
console. log ( 'init' )
}
fun ( ) {
console. log ( 'fun' )
}
}
class Factory {
create ( name ) {
return new Product ( name)
}
}
let factory = new Factory ( )
let p = factory. create ( 'p1' )
p. init ( )
p. fun ( )
抽象工厂模式
了简单工厂模式和工厂方法模式都是直接生成实例,但是抽象工厂模式不同,抽象工厂模式并不直接生成实例, 而是用于对产品类簇的创建,抽象工厂其实是实现子类继承父类的方法 应用实例:上面例子中的superAdmin,admin,user三种用户角色,其中user可能是使用不同的社交媒体账户进行注册的,例如:wechat,qq,weibo。那么这三类社交媒体账户就是对应的类簇。
function getAbstractUserFactory ( type ) {
switch ( type) {
case 'wechat' :
return UserOfWechat;
break ;
case 'qq' :
return UserOfQq;
break ;
case 'weibo' :
return UserOfWeibo;
break ;
default :
throw new Error ( '参数错误, 可选参数:superAdmin、admin、user' )
}
}
let WechatUserClass = getAbstractUserFactory ( 'wechat' ) ;
let QqUserClass = getAbstractUserFactory ( 'qq' ) ;
let WeiboUserClass = getAbstractUserFactory ( 'weibo' ) ;
let wechatUser = new WechatUserClass ( '微信小李' ) ;
let qqUser = new QqUserClass ( 'QQ小李' ) ;
let weiboUser = new WeiboUserClass ( '微博小李' ) ;
代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。 常用的虚拟代理形式:某一个花销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建 应用实例:假设当A 在心情好的时候收到花,小明表白成功的几率有 60%,而当A 在心情差的时候收到花,小明表白的成功率无限趋近于0。 小明跟A 刚刚认识两天,还无法辨别A 什么时候心情好。如果不合时宜地把花送给A,花 被直接扔掉的可能性很大,这束花可是小明吃了7 天泡面换来的。 但是A 的朋友B 却很了解A,所以小明只管把花交给B,B 会监听A 的心情变化,然后选 择A 心情好的时候把花转交给A:
let Flower = function ( ) {
}
let xiaoming = {
sendFlower : function ( target ) {
let flower = new Flower ( )
target. receiveFlower ( flower)
}
}
let B = {
receiveFlower : function ( flower ) {
A . listenGoodMood ( function ( ) {
A . receiveFlower ( flower)
} )
}
}
let A = {
receiveFlower : function ( flower ) {
console. log ( '收到花' + flower)
} ,
listenGoodMood : function ( fn ) {
setTimeout ( function ( ) {
fn ( )
} , 1000 )
}
}
xiaoming. sendFlower ( B )
外观模式
定义:为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。 用法:原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。 场景: 设计初期,应该要有意识地将不同的两个层分离,比如经典的三层结构,在数据访问层和业务逻辑层、业务逻辑层和表示层之间建立外观Facade 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少他们之间的依赖。 在维护一个遗留的大型系统时,可能这个系统已经很难维护了,这时候使用外观Facade也是非常合适的,为系系统开发一个外观Facade类,为设计粗糙和高度复杂的遗留代码提供比较清晰的接口,让新系统和Facade对象交互,Facade与遗留代码交互所有的复杂工作。
let addMyEvent = function ( el, ev, fn ) {
if ( el. addEventListener) {
el. addEventListener ( ev, fn, false )
} else if ( el. attachEvent) {
el. attachEvent ( 'on' + ev, fn)
} else {
el[ 'on' + ev] = fn
}
} ;
let myEvent = {
stop : e => {
e. stopPropagation ( ) ;
e. preventDefault ( ) ;
}
} ;
装饰者模式
定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。 用法:原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。 场景:用AOP装饰函数实现装饰者模式
Function . prototype. before = function ( beforefn ) {
var self = this ;
return function ( ) {
beforefn . apply ( this , arguments) ;
return self . apply ( this , arguments) ;
}
}
Function . prototype. after = function ( afterfn ) {
var self = this ;
return function ( ) {
var ret = self . apply ( this , arguments) ;
afterfn . apply ( this , arguments) ;
return ret;
}
}
var func = function ( ) {
console. log ( '2' ) ;
}
var func1 = function ( ) {
console. log ( '1' ) ;
}
var func3 = function ( ) {
console. log ( '3' ) ;
}
func = func. before ( func1) . after ( func3) ;
func ( ) ;
观察者模式
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
class Subject {
constructor ( ) {
this . state = 0 ;
this . observers = [ ] ;
}
getState ( ) {
return this . state;
}
setState ( state ) {
this . state = state;
this . notify ( ) ;
}
notify ( ) {
this . observers. forEach ( observer => {
observer. update ( ) ;
} )
}
attach ( observer ) {
this . observers. push ( 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 ( ) } ` ) ;
}
}
let sub = new Subject ( ) ;
let observer1 = new Observer ( 'o1' , sub) ;
let observer2 = new Observer ( 'o2' , sub) ;
sub. setState ( 1 ) ;
发布-订阅模式
定义:是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。 用法:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
实现思路
创建一个对象 在该对象上创建一个缓存列表(调度中心) on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心) emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码) off 方法可以根据 event 值取消订阅(取消订阅) once 方法只监听一次,调用完毕后删除缓存函数(订阅一次) 应用实例: 订阅公众号
let eventEmitter = { } ;
eventEmitter. list = { } ;
eventEmitter. on = function ( event, fn ) {
let _this = this ;
( _this. list[ event] || ( _this. list[ event] = [ ] ) ) . push ( fn) ;
return _this;
} ;
eventEmitter. emit = function ( ) {
let _this = this ;
let event = [ ] . shift . call ( arguments) ,
fns = [ ... _this. list[ event] ] ;
if ( ! fns || fns. length === 0 ) {
return false ;
}
fns. forEach ( fn => {
fn . apply ( _this, arguments) ;
} ) ;
return _this;
} ;
function user1 ( content ) {
console. log ( '用户1订阅了:' , content) ;
} ;
function user2 ( content ) {
console. log ( '用户2订阅了:' , content) ;
} ;
eventEmitter. on ( 'article' , user1) ;
eventEmitter. on ( 'article' , user2) ;
eventEmitter. emit ( 'article' , 'Javascript 发布-订阅模式' ) ;
Vue 中的实现
var hookRE = / ^hook: / ;
Vue . prototype. $on = function ( event, fn ) {
var this $1 = this ;
var vm = this ;
if ( Array. isArray ( event) ) {
for ( var i = 0 , l = event. length; i < l; i++ ) {
this $1. $on ( event[ i] , fn) ;
}
} else {
( vm. _events[ event] || ( vm. _events[ event] = [ ] ) ) . push ( fn) ;
if ( hookRE. test ( event) ) {
vm. _hasHookEvent = true ;
}
}
return vm
}
Vue . prototype. $emit = function ( event ) {
var vm = this ;
var cbs = vm. _events[ event] ;
if ( cbs) {
cbs = cbs. length > 1 ? toArray ( cbs) : cbs;
var args = toArray ( arguments, 1 ) ;
for ( var i = 0 , l = cbs. length; i < l; i++ ) {
try {
cbs[ i] . apply ( vm, args) ;
} catch ( e) {
handleError ( e, vm, ( "event handler for \"" + event + "\"" ) ) ;
}
}
}
return vm
}
发布-订阅模式与观察者模式的区别
观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
差异:
在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
策略模式
定义:定为一个对象提供一个代用品或占位符,以便控制对它的访问。 主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。 应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略
var levelOBJ = {
"A" : function ( money ) {
return money * 4 ;
} ,
"B" : function ( money ) {
return money * 3 ;
} ,
"C" : function ( money ) {
return money * 2 ;
}
} ;
var calculateBouns = function ( level, money ) {
return levelOBJ[ level] ( money) ;
} ;
console. log ( calculateBouns ( 'A' , 10000 ) ) ;
状态模式
定义:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 何时使用:代码中包含大量与对象状态有关的条件语句。 应用实例:红绿灯
class RedLight {
constructor ( state ) {
this . state = state;
}
light ( ) {
console. log ( 'turn to red light' ) ;
this . state. setState ( this . state. greenLight)
}
}
class greenLight {
constructor ( state ) {
this . state = state;
}
light ( ) {
console. log ( 'turn to green light' ) ;
this . state. setState ( this . state. yellowLight)
}
}
class yellowLight {
constructor ( state ) {
this . state = state;
}
light ( ) {
console. log ( 'turn to yellow light' ) ;
this . state. setState ( this . state. redLight)
}
}
class State {
constructor ( ) {
this . redLight = new RedLight ( this )
this . greenLight = new greenLight ( this )
this . yellowLight = new yellowLight ( this )
this . setState ( this . redLight)
}
setState ( state ) {
this . currState = state;
}
}
const state = new State ( ) ;
state. currState. light ( )
setInterval ( ( ) => {
state. currState. light ( )
} , 3000 )