去年的时候先是看了修言大佬的性能优化掘金小册子,收获良多。javascript
以后紧接着买了这本JavaScript 设计模式核⼼原理与应⽤实践,恰好最近有小册免费学的活动,就赶忙把这篇笔记整理出来了,而且补充了小册子中的没有写到的其他设计模式,学习过程当中结合 JavaScript 编写的例子,以便于理解和加深印象。html
与其说是一篇文章,其实更像是一篇总结性质的学习笔记。前端
学习以前,先了解什么是设计模式?java
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提升代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
简答理解 它是一套被反复使用、多人知晓的、通过分类的、代码设计经验总结。node
烹饪有菜谱,游戏有攻略,每一个领域都存在一些可以让咱们又好又快地达成目标的“套路”。在程序世界,编程的“套路”就是设计模式。webpack
学习它也就是学习这个编程世界的套路,对之后升级打怪打装备有很大的帮助。在瞬息万变的前端领域,设计模式也是一种“一次学习,终生受用”知识。ios
描述一个不断发生的重复的问题,以及该问题的解决方案的核心。
这样,你就能一次又一次的使用该方案而没必要作重复劳动。
一大法则:git
五大原则:es6
JavaScript 中经常使用的是单一功能和开放封闭原则。github
经过设计模式能够帮助咱们加强代码的可重用性、可扩充性、 可维护性、灵活性好。咱们使用设计模式最终的目的是实现代码的 高内聚 和 低耦合。
举例一个现实生活中的例子,例如一个公司,通常都是各个部门各司其职,互不干涉。各个部门须要沟通时经过专门的负责人进行对接。
在软件里面也是同样的 一个功能模块只是关注一个功能,一个模块最好只实现一个功能,这个是所谓的内聚。
模块与模块之间、系统与系统之间的交互,是不可避免的, 可是咱们要尽可能减小因为交互引发的单个模块没法独立使用或者没法移植的状况发生, 尽量多的单独提供接口用于对外操做, 这个就是所谓的低耦合
在实际开发过程当中,不发生变化的代码基本是不存在的,因此我要将代码的变化最小化。
设计模式的核心就是去观察你整个逻辑里的变与不变,而后将不变分离,达到使变化的部分灵活、不变的地方稳定的目的。
经常使用的能够分为建立型、结构型、行为型三类,一共 23 种模式。
建立型:
结构型:
行为型:
这种类型的设计模式属于建立型模式,它提供了一种建立对象的最佳方式。
在工厂模式中,咱们在建立对象时不会对客户端暴露建立逻辑,而且是经过使用一个共同的接口来指向新建立的对象。
在 JS 中其实就是借助构造函数实现。
例子
某个班级要作一个录入系统,录入一我的,就要写一次。
let liMing = { name: "李明", age: 20, sex: "男", };
若是多个录入,则能够建立一个类。
class Student { constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } } let zhangSan = new Student("张三", 19, "男");
工厂模式是将建立对象的过程单独封装,使用使只须要无脑传参就好了,就像一个工厂同样,只要给够原料,就能够轻易的制造出成品。
小结
单例模式的定义:保证一个类仅有一个实例,而且提供一个访问它的全局变量。
实现的方法为前判断实例是否存在,若是存在直接返回,不存在则建立在返回,这就确保了一个类只有一个实例对象。
好比:Vuex、jQuery
例子
使用场景:一个单一对象,好比:弹窗,不管点击多少次,弹窗只应被建立一次,实现起来也很简单,用一个变量缓存就好了。
【点击查看Demo】:单例模式-在线例子
如上面这个弹框,只有在第一次点击按钮时才会建立弹框,以后都不会在建立,而是使用以前建立的弹框。
如此,即是实现了一个应用于单例模式的弹框。
小结
用原型实例指定建立对象的种类,而且经过拷贝这些原型建立新的对象。
例子
在 JavaScript 中,实现原型模式是在 ECMAscript5 中,提出的 Object.create 方法,使用现有的对象来提供建立的对象__proto__
。
var prototype = { name: "Jack", getName: function() { return this.name; }, }; var obj = Object.create(prototype, { job: { value: "IT", }, }); console.log(obj.getName()); // Jack console.log(obj.job); // IT console.log(obj.__proto__ === prototype); //true
有原型就有原理性了
在面向对象的编程语言中,构造器是一个类中用来初始化新对象的特殊方法。而且能够接受参数用来设定实例对象的属性的方法
function Car(model, year, miles) { this.model = model; this.year = year; this.miles = miles; // this.info = new CarDetail(model) // 属性也能够经过 new 的方式产生 } // 覆盖原型对象上的toString Car.prototype.toString = function() { return this.model + " has done " + this.miles + " miles"; }; // 使用: var civic = new Car("Honda Civic", 2009, 20000); var mondeo = new Car("Ford Mondeo", 2010, 5000); console.log(civic.toString()); // Honda Civic has done 20000 miles console.log(mondeo.toString()); // Ford Mondeo has done 5000 miles
其实就是利用原型链上被继承的特性,实现了构造器。
抽象工厂模式(Abstract Factory)就是经过类的抽象使得业务适用于一个产品类簇的建立,而不负责某一类产品的实例。
JS 中是没有直接的抽象类的,abstract 是个保留字,可是尚未实现,所以咱们须要在类的方法中抛出错误来模拟抽象类,若是继承的子类中没有覆写该方法而调用,就会抛出错误。
const Car = function() {}; Car.prototype.getPrice = function() { return new Error("抽象方法不能调用"); };
面向对象的语言里有抽象工厂模式,首先声明一个抽象类做为父类,以归纳某一类产品所须要的特征,继承该父类的子类须要实现父类中声明的方法而实现父类中所声明的功能:
/** * 实现subType类对工厂类中的superType类型的抽象类的继承 * @param subType 要继承的类 * @param superType 工厂类中的抽象类type */ const VehicleFactory = function(subType, superType) { if (typeof VehicleFactory[superType] === "function") { function F() { this.type = "车辆"; } F.prototype = new VehicleFactory[superType](); subType.constructor = subType; subType.prototype = new F(); // 由于子类subType不只须要继承superType对应的类的原型方法,还要继承其对象属性 } else throw new Error("不存在该抽象类"); }; VehicleFactory.Car = function() { this.type = "car"; }; VehicleFactory.Car.prototype = { getPrice: function() { return new Error("抽象方法不可以使用"); }, getSpeed: function() { return new Error("抽象方法不可以使用"); }, }; const BMW = function(price, speed) { this.price = price; this.speed = speed; }; VehicleFactory(BMW, "Car"); // 继承Car抽象类 BMW.prototype.getPrice = function() { // 覆写getPrice方法 console.log(`BWM price is ${this.price}`); }; BMW.prototype.getSpeed = function() { console.log(`BWM speed is ${this.speed}`); }; const baomai5 = new BMW(30, 99); baomai5.getPrice(); // BWM price is 30 baomai5 instanceof VehicleFactory.Car; // true
经过抽象工厂,就能够建立某个类簇的产品,而且也能够经过 instanceof 来检查产品的类别,也具有该类簇所必备的方法。
装饰器模式,又名装饰者模式。它的定义是“ 在不改变原对象的基础上,经过对其进行包装拓展,使原有对象能够知足用户的更复杂需求 ”。
装饰器案例
有一个弹窗函数,点击按钮后会弹出一个弹框。
function openModal() { let div = document.craeteElement("div"); div.id = "modal"; div.innerHTML = "提示"; div.style.backgroundColor = "gray"; document.body.appendChlid(div); } btn.onclick = () => { openModal(); };
可是突然产品经理要改需求,要把提示文字由“提示”改成“警告”,背景颜色由 gray 改成 red。
听到这个你是否是立马就想直接改动源函数:
function openModal() { let div = document.craeteElement("div"); div.id = "modal"; div.innerHTML = "警告"; div.style.backgroundColor = "red"; document.body.appendChlid(div); }
可是若是是复杂的业务逻辑,或者这个代码时上任代码留下来的产物,在考虑到之后的需求变化,每次都这样修改确实很麻烦。
并且,直接修改已有的函数体,有违背了咱们的“开放封闭原则”,往一个函数塞这么多的逻辑,也违背了“单一职责原则”,因此上面的方法并非最佳的。
最省时省力的方式是不去关心它现有得了逻辑,只在此逻辑之上扩展新的功能便可,所以装饰器模式就此而生。
// 新逻辑 function changeModal() { let div = document.getElemnetById("modal"); div.innerHTML = "告警"; div.style.backgroundColor = "red"; } btn.onclick = () => { openModal(); changeModal(); };
这种经过函数添加新的功能、而又不修改旧逻辑,这就是装饰器的魅力。
ES7 中的装饰器
在最新的 ES7 中有装饰器的提案,可是还未定案,因此语法可能不是最终版,可是思想是同样的。
@tableColor class Table { // ... } function tableColor(target) { target.color = "red"; } Table.color; // true
为Table
这个类,添加一个tableColor
的装饰器,便可改变Table
的color
属性
class Person { @readonly name() { return `${this.first} ${this.last}`; } }
为Person
类的name
方法添加只读的装饰器,使得该方法不可被修改。
实际上是借助Object.defineProperty
的wirteable
特性实现的。
装饰函数
由于 JS 中函数存在函数提高,直接使用装饰器并不可取,可是可使用高级函数的方式实现。
function doSomething(name) { console.log("Hello, " + name); } function loggingDecorator(wrapped) { return function() { console.log("fun-Starting"); const result = wrapped.apply(this, arguments); console.log("fun-Finished"); return result; }; } const wrapped = loggingDecorator(doSomething); let name = "World"; doSomething(name); // 装饰前 // output: // Hello, World wrapped(name); // 装饰后 // output: // fun-Starting // Hello, World // fun-Finished
上面的装饰器,是给一个函数在执行开始和执行结束分别打印一个 log。
参考
适配器模式的做用是解决两个软件实体间的接口不兼容问题。使用适配器模式以后,本来因为接口不兼容而不能工做的两个软件实体能够一块儿工做。
简单来讲,就是把一个类的接口变成客户端期待的另外一种接口,解决兼容问题。
好比:axios
例子:一个渲染地图的方法,默认是调用当前地图对象的 show 方法进行渲染操做,当有多个地图,而每一个地图的渲染方法都不同时,为了方便使用者调用,就须要作适配了。
let googleMap = { show: () => { console.log("开始渲染谷歌地图"); }, }; let baiduMap = { display: () => { console.log("开始渲染百度地图"); }, }; let baiduMapAdapter = { show: () => { return baiduMap.display(); }, }; function renderMap(obj) { obj.show(); } renderMap(googleMap); // 开始渲染谷歌地图 renderMap(baiduMapAdapter); // 开始渲染百度地图
这其中对“百度地图”作了适配的处理。
小结
代理模式——在某些状况下,出于种种考虑/限制,一个对象不能直接访问另外一个对象,须要一个第三者(代理)牵桥搭线从而间接达到访问目的,这样的模式就是代理模式。
提起代理(Proxy),对于前端很熟悉的,我能联想到一系列的东西,好比:
事件代理
常见的列表、表格都须要单独处理事件时,使用父级元素事件代理,能够极大的减小代码量。
<div id="father"> <span id="1">新闻1</span> <span id="2">新闻2</span> <span id="3">新闻3</span> <span id="4">新闻4</span> <span id="5">新闻5</span> <span id="6">新闻6</span> <!-- 七、8... --> </div>
如上代码,我想点击每一个新闻,均可以拿到当前新闻的id
,从而进行下一步操做。
若是给每个span
都绑定一个onclick
事件,就太耗费性能了,并且写起来也很麻烦。
咱们常见的作法是利用事件冒泡的原理,将事件带代理到父元素上,而后统一处理。
let father = document.getElementById("father"); father.addEventListener("click", (evnet) => { if (event.target.nodeName === "SPAN") { event.preventDefault(); let id = event.target.id; console.log(id); // 拿到id,进行下一步操做 } });
虚拟代理
例如:某个花销很大的操做,能够经过虚拟代理的方式延迟到这种须要它的时候才去建立(例如:使用虚拟代理实现图片懒加载)
图片预加载:先经过一张 loading 图占位,而后经过异步的方式加载图片,等图片加载完成以后在使用原图替换 loading 图。
问什么要使用预加载+懒加载?以淘宝举例,商城物品图片多之又多,一次所有请求过来这么多图片不管是对 js 引擎仍是浏览器自己都是一个巨大的工做量,会拖慢浏览器响应速度,用户体验极差,而预加载+懒加载的方式会大大节省浏览器请求速度,经过预加载率先加载占位图片(第二次及之后都是缓存中读取),再经过懒加载直到要加载的真实图片加载完成,瞬间替换。这种模式很好的解决了图片一点点展示在页面上用户体验差的弊端。
须知:图片第一次设置 src,浏览器发送网络请求;若是设置一个请求过的 src 那么浏览器则会从缓存中读取 from disk cache
class PreLoadImage { constructor(imgNode) { // 获取真实的DOM节点 this.imgNode = imgNode; } // 操做img节点的src属性 setSrc(imgUrl) { this.imgNode.src = imgUrl; } } class ProxyImage { // 占位图的url地址 static LOADING_URL = "xxxxxx"; constructor(targetImage) { // 目标Image,即PreLoadImage实例 this.targetImage = targetImage; } // 该方法主要操做虚拟Image,完成加载 setSrc(targetUrl) { // 真实img节点初始化时展现的是一个占位图 this.targetImage.setSrc(ProxyImage.LOADING_URL); // 建立一个帮咱们加载图片的虚拟Image实例 const virtualImage = new Image(); // 监听目标图片加载的状况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url virtualImage.onload = () => { this.targetImage.setSrc(targetUrl); }; // 设置src属性,虚拟Image实例开始加载图片 virtualImage.src = targetUrl; } }
ProxyImage
帮咱们调度了预加载相关的工做,咱们能够经过 ProxyImage
这个代理,实现对真实 img 节点的间接访问,并获得咱们想要的效果。
在这个实例中,virtualImage
这个对象是一个“幕后英雄”,它始终存在于 JavaScript 世界中、代替真实 DOM 发起了图片加载请求、完成了图片加载工做,却从未在渲染层面抛头露面。所以这种模式被称为“虚拟代理”模式。
【点击查看Demo】:虚拟代理-在线例子
缓存代理
缓存代理比较好理解,它应用于一些计算量较大的场景里。在这种场景下,咱们须要“用空间换时间”——当咱们须要用到某个已经计算过的值的时候,不想再耗时进行二次计算,而是但愿能从内存里去取出现成的计算结果。
这种场景下,就须要一个代理来帮咱们在进行计算的同时,进行计算结果的缓存了。
例子:对参数求和函数进行缓存代理。
// addAll方法会对你传入的全部参数作求和操做 const addAll = function() { console.log("进行了一次新计算"); let result = 0; const len = arguments.length; for (let i = 0; i < len; i++) { result += arguments[i]; } return result; }; // 为求和方法建立代理 const proxyAddAll = (function() { // 求和结果的缓存池 const resultCache = {}; return function() { // 将入参转化为一个惟一的入参字符串 const args = Array.prototype.join.call(arguments, ","); // 检查本次入参是否有对应的计算结果 if (args in resultCache) { // 若是有,则返回缓存池里现成的结果 console.log("无计算-使用缓存的数据"); return resultCache[args]; } return (resultCache[args] = addAll(...arguments)); }; })(); let sum1 = proxyAddAll(1, 2, 3); // 进行了一次新计算 let sum2 = proxyAddAll(1, 2, 3); // 无计算-使用缓存的数据
第一次进行计算返回结果,并存入缓存。若是再次传入相同的参数,则不计算,直接返回缓存中存在的结果。
在常见在 HTTP 缓存中,浏览器就至关于进行了一层代理缓存,经过 HTTP 的缓存机制控制(强缓存和协商缓存)判断是否启用缓存。
频繁却变化小的的网络请求,好比getUserInfo
,可使用代理请求,设置统一发送和存取。
小结
桥接模式:将抽象部分和具体实现部分分离,二者可独立变化,也能够一块儿工做。
在这种模式的实现上,须要一个对象担任“桥”的角色,起到链接的做用。
例子:
JavaScript 中桥接模式的典型应用是:Array
对象上的forEach
函数。
此函数负责循环遍历数组每一个元素,是抽象部分; 而回调函数callback
就是具体实现部分。
下方是模拟forEach
方法:
const forEach = (arr, callback) => { if (!Array.isArray(arr)) return; const length = arr.length; for (let i = 0; i < length; ++i) { callback(arr[i], i); } }; // 如下是测试代码 let arr = ["a", "b"]; forEach(arr, (el, index) => console.log("元素是", el, "位于", index)); // 元素是 a 位于 0 // 元素是 b 位于 1
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端能够访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
例子
外观模式即执行一个方法可让多个方法一块儿被调用。
涉及到兼容性,参数支持多个格式、环境等等.. 对外暴露统一的 api
好比本身封装的事件对象包含了阻止冒泡和添加事件监听的兼容方法:
const myEvent = { stop (e){ if(typeof e.preventDefault() == 'function'){ e.preventDefault(); } if(typeof e.stopPropagation() == 'function'){ e.stopPropagation() } // IE if(typeOd e.retrunValue === 'boolean'){ e.returnValue = false } if(typeOd e.cancelBubble === 'boolean'){ e.returnValue = true } } addEvnet(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 } } }
组合模式(Composite Pattern),又叫部分总体模式,是用于把一组类似的对象看成一个单一的对象。
组合模式依据树形结构来组合对象,用来表示部分以及总体层次。这种类型的设计模式属于结构型模式,它建立了对象组的树形结构。
这种模式建立了一个包含本身对象组的类。该类提供了修改相同对象组的方式。
例子
想象咱们如今手上有多个万能遥控器,当咱们回到家中,按一下开关,下列事情将被执行
// 先准备一些须要批量执行的功能 class GoHome { init() { console.log("开门"); } } class OpenComputer { init() { console.log("开电脑"); } } class OpenMusic { init() { console.log("开音乐"); } } // 组合器,用来组合功能 class Comb { constructor() { // 准备容器,用来防止未来组合起来的功能 this.skills = []; } // 用来组合的功能,接收要组合的对象 add(task) { // 向容器中填入,未来准备批量使用的对象 this.skills.push(task); } // 用来批量执行的功能 action() { // 拿到容器中全部的对象,才能批量执行 this.skills.forEach((val) => { val.init(); }); } } // 建立一个组合器 let c = new Comb(); // 提早将,未来要批量操做的对象,组合起来 c.add(new GoHome()); // 添加'开门'命令 c.add(new OpenComputer()); // 添加'开电脑'命令 c.add(new OpenMusic()); // 添加'开音乐'命令 c.action(); // 执行添加的全部命令
小结
树形
结构被一致对待
根部进行调用
批量执行
享元模式(Flyweight Pattern)主要用于减小建立对象的数量,以减小内存占用和提升性能。
这种类型的设计模式属于结构型模式,它提供了减小对象数量从而改善应用所需的对象结构的方式。
特色
例子
好比常见的事件代理,经过将若干个子元素的事件代理到一个父元素,子元素共同使用一个方法。若是都绑定到<span>
标签,对内存开销太大 。
<!-- 点击span,拿到当前的span中的内容 --> <div id="box"> <span>1</span> <span>2</span> <span>3</span> <span>4</span> </div> <script> var box = document.getElementById("box"); box.addEventListener("click", function(e) { let target = e.target; if (e.nodeName === "SPAN") { alert(target.innerHTML); } }); </script>
小结
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露对象的对象的内部表示。
迭代器模式能够把迭代的过程从业务逻辑中分离出来,在使用迭代器模式以后,及时不关心对象的内部构造,也能够按照顺序访问其中的每一个元素。
简单类说,它的目的就是去遍历一个可遍历的对象。
像 JS 中原生的 forEach、map 等方法都属因而迭代器模式的一种实现,通常来讲不用本身去实现迭代器。
在 JS 中有一种类数组的存在,他们没有迭代方法,好比 nodeList、arguments 并不能直接使用迭代方法,须要使用 jQuery 的 each 方法或者将类数组装换为真正的数组在进行迭代。
而在最新的 ES6 中,对有只要有 Iterator 接口的数据类型均可以使用 for..of..进行遍历,而他的底层则是对 next 方法的反复调用,具体参考阮一峰-Iterator 和 for...of 循环。
例子
咱们能够借助 Iterator 接口本身实现一个迭代器。
class Creater { constructor(list) { this.list = list; } // 建立一个迭代器,也叫遍历器 createIterator() { return new Iterator(this); } } class Iterator { constructor(creater) { this.list = creater.list; this.index = 0; } // 判断是否遍历完数据 isDone() { if (this.index >= this.list.length) { return true; } return false; } next() { return this.list[this.index++]; } } var arr = [1, 2, 3, 4]; var creater = new Creater(arr); var iterator = creater.createIterator(); console.log(iterator.list); // [1, 2, 3, 4] while (!iterator.isDone()) { console.log(iterator.next()); // 1 // 2 // 3 // 4 }
小结
发布/订阅模式又叫观察者模式,她定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,全部依赖他的对象都将获得通知。在 JavaScrtipt 中,咱们通常使用时间模型来替代传统的发布/订阅模式。
好比:Vue 中的双向绑定和事件机制。
发布/订阅模式和观察者模式的区别
发布者不直接触及到订阅者,而是由统一的第三方完成通讯操做,叫发布/订阅模式
例子
能够本身实现一个事件总线,模拟$emit
和$on
class EventBus { constructor() { this.callbacks = {}; } $on(name, fn) { (this.callbacks[name] || (this.callbacks[name] = [])).push(fn); } $emit(name, args) { let cbs = this.callbacks[name]; if (cbs) { cbs.forEach((c) => { c.call(this, args); }); } } $off(name) { this.callbacks[name] = null; } } let event = new EventBus(); event.$on("event1", (arg) => { console.log("event1", arg); }); event.$on("event2", (arg) => { console.log("event2", arg); }); event.$emit("event1", 1); // event1 1 event.$emit("event2", 2); // event2 2
定义一系列的算法,把他们一个个封装起来,并使他们能够替换。
策略模式的目的就是将算法的使用和算法的实现分离开来。
一个策略模式一般由两部分组成:
说明环境类要维持对某个策略对象的引用。
例子
经过绩效等级计算奖金,能够轻易的写出以下的代码:
var calculateBonus = function(performanceLevel, salary) { if (performanceLevel === "S") { return salary * 4; } if (performanceLevel === "A") { return salary * 3; } if (performanceLevel === "B") { return salary * 2; } }; calculateBonus("B", 20000); // 输出:40000 calculateBonus("S", 6000); // 输出:24000
使用策略模式修改代码:
var strategies = { S: (salary) => { return salary * 4; }, A: (salary) => { return salary * 3; }, B: (salary) => { return salary * 2; }, }; var calculateBonus = function(level, salary) { return strategies[level](salary); }; console.log(calculateBonus("S", 200)); // 输出:800 console.log(calculateBonus("A", 200)); // 输出:600
状态模式容许一个对象在其内部状态改变的时候改变
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的状况。把状态的判断逻辑转移到表示不一样状态的一系列类中,能够把复杂的判断逻辑简化。
例子
实现一个交通灯的切换。
点击查看Demo:交通讯号灯-在线例子
这时候若是在加一个蓝光的话,能够直接添加一个蓝光的类,而后添加 parssBtn 方法,其余状态都不须要变化。
小结
解释器模式(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
用到的比较少,能够参考两篇文章来理解。
小结
中介者模式(Mediator Pattern)是用来下降多个对象和类之间的通讯复杂性。
这种模式提供了一个中介类,该类一般处理不一样类之间的通讯,并支持松耦合,使代码易于维护
经过一个中介者对象,其余全部相关对象都经过该对象来通讯,而不是相互引用,但其中一个对象发生改变时,只须要通知中介者对象便可。
经过中介者模式能够解除对象与对象以前的耦合关系。
例如:Vuex
参考连接:JavaScript 中介者模式
小结
在访问者模式(Visitor Pattern)中,咱们使用了一个访问者类,它改变了元素类的执行算法。
经过这种方式,元素的执行算法能够随着访问者改变而改变。
例子
经过访问者调用元素类的方法。
// 访问者 function Visitor() { this.visit = function(concreteElement) { concreteElement.doSomething(); // 谁访问,就使用谁的doSomething() }; } // 元素类 function ConceteElement() { this.doSomething = function() { console.log("这是一个具体元素"); }; this.accept = function(visitor) { visitor.visit(this); }; } // Client var ele = new ConceteElement(); var v = new Visitor(); ele.accept(v); // 这是一个具体元素
小结
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象
例子
实现一个带有保存记录功能的”编辑器“,功能包括
// 状态备忘 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("111"); editor.setContent("222"); careTaker.add(editor.saveContentToMemento()); // 存储备忘录 editor.setContent("333"); careTaker.add(editor.saveContentToMemento()); // 存储备忘录 editor.setContent("444"); console.log(editor.getContent()); // 444 editor.getContentFromMemento(careTaker.get(1)); // 撤销 console.log(editor.getContent()); // 333 editor.getContentFromMemento(careTaker.get(0)); // 撤销 console.log(editor.getContent()); // 222
小结
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。
它的子类能够按须要重写方法实现,但调用将以抽象类中定义的方式进行。
感受用到的不是不少,想了解的能够点击下面的参考连接。
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求建立了一个接收者对象的链。
这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
在这种模式中,一般每一个接收者都包含对另外一个接收者的引用。
若是一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
例子
公司的报销审批流程:组长=》项目经理=》财务总监
// 请假审批,须要组长审批、经理审批、最后总监审批 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(); // 组长 审批 // 项目经理 审批 // 财务总监 审批 // 将一步操做分为多个职责来完成,一个接一个的执行,最终完成操做。
小结
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。
请求以命令的形式包裹在对象中,并传给调用对象。
调用对象寻找能够处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
例子
实现一个编辑器,有不少命令,好比:写入、读取等等。
class Editor { constructor() { this.content = ""; this.operator = []; } write(content) { this.content += content; } read() { console.log(this.content); } space() { this.content += " "; } readOperator() { console.log(this.operator); } run(...args) { this.operator.push(args[0]); this[args[0]].apply(this, args.slice(1)); return this; } } const editor = new Editor(); editor .run("write", "hello") .run("space") .run("write", "zkk!") .run("read"); // => 'hello zkk!' // 输出操做队列 editor.readOperator(); // ["write", "space", "write", "read"]
小结
如下是摘抄自掘金小册-JavaScript 设计模式核⼼原理与应⽤实践的结语。
设计模式的征程,到此就告一段落了。但对各位来讲,真正的战斗才刚刚开始。设计模式的魅力,不在纸面上,而在实践中。
学设计模式:
一在多读——读源码,读资料,读好书;
二在多练——把你学到的东西还原到业务开发里去,看看它是否 OK,有没有问题?若是有问题,如何修复、如何优化?没有一种设计模式是完美的,设计模式和人同样,处在动态发展的过程当中,并非只有 GOF 提出的 23 种设计模式能够称之为设计模式。
只要一种方案遵循了设计原则、解决了一类问题,那么它均可以被冠以“设计模式”的殊荣。
在各位从设计模式小册毕业之际,但愿你们带走的不止是知识,还有好的学习习惯、阅读习惯。最重要的,是深挖理论知识的勇气和技术攻关的决心。这些东西不是所谓“科班”的专利,而是一个优秀工程师的必须。
来自九旬的原创: 博客原文连接