前言
此篇文章会一直持续更新
我们写代码到底是在写什么
设计模式扮演的角色
- 帮助我们组织模块
- 帮助我们设计沟通
- 提高代码质量
设计原则
- 开闭原则
- 单一职责原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特法则
- 里氏替换原则
设会模式分类
- 创建型
- 结构型
- 行为型
- 技巧型
创建型
- 工厂模式 -- 大量创建对象
- 单例模式 -- 全局只能有我一个
- 建造者模式 -- 精细化组合对象
- 原型模式 -- Javascript的灵魂
封装与对象
封装目的
- 定义变量不会污染外部;
- 能够作为一个模块调用;
- 遵循开闭原则;
什么是好的封装
- 变量外部不可见;
- 调用接口使用;
- 留出扩展接口;
创建对象时的设计模式
- 工厂模式
- 目的:方便我们大量创建对象
- 应用场景:当某一个对象需要经常创建的时候、如分页对象的获取
- 基本结构:写一个方法,只需要调用这个方法,就能拿到你要的对象
function Factory(type) { switch (type) { case 'type1': return new Type1(); case 'type2': return new Type2(); case 'type3': return new Type3(); } }- 示例:多彩的弹窗(需求:项目有一个弹窗需求,弹窗有多种,他们之间存在内容和颜色上的差异)
(function() { function pop(type, content, color) { if (this instanceof pop) { var s = new this[type](content, color); } else { return new pop(type, content, color); } } pop.prototype.infoPop = function(content, color) { console.log('消息弹框:' + content + color); } pop.prototype.cancelPop = function(content, color) { console.log('取消消息弹框:' + content + color); } pop.prototype.confirmPop = function(content, color) { console.log('确认消息弹框:' + content + color); } window.pop = pop; })(); var arr = [{ type: 'infoPop', content: 'hello', color: 'yellow' }, { type: 'cancelPop', content: '取消', color: 'red' }, { type: 'confirmPop', content: '确认', color: 'green' }]; arr.forEach(item => { window.pop(item.type, item.content, item.color); })- 源码示例:jquery (需求:jQuery需要操作dom,每一个dom都是一个jquery对象)
(function() { var jquery = function(selector, contentx) { return new jquery.fn.init(selector, contentx); } jquery.fn = jquery.prototype = { init: function() { } } jquery.fn.init.prototype = jquery.fn; jquery.extend = jquery.fn.extend = function() { } jquery.extend({ }) window.$ = window.jquery = jquery; })(); - 建造者模式
- 目的:需要组合出一个全局对象
- 应用场景:当要创建单个、庞大的组合对象时,如轮播图的实现
- 基本结构:把一个复杂的类各个部分,拆分成独立的类,然后再在最终类里组合到一块,final为最终给出去的类
function Module1() { } function Module2() { } function Final() { this.module1 = new Module1(); this.module2 = new Module2(); }- 示例:编写一个编辑器插件(需求:有一个编辑器插件,初始化的时候需要配置大量的参数,而且内部功能很多)
- 源码实例:Vue的初始化(vue内部众多模块,而且过程复杂)
function Vue(options) { if (!(this instanceof Vue)) { console.warn('请使用new操作符'); } this._init(options); } //混入 initMixin(Vue); stateMixin(Vue); eventsMixin(Vue); lifecycleMixin(Vue); renderMixin(Vue); - 单例模式
- 目的:需要确保全局只有一个对象
- 应用场景:为了避免重复创建,避免多个对象存在互相干扰
- 基本结构:通过定义一个方法,使用时只允许通过此方法拿到存在内部的同一实例化对象
let Singleton = function(name) { this.name = name; } Singleton.getInstance = function(name) { if (this.instance) { return this.instance; } return this.instance = new Singleton(name); }- 示例:全局的数据存储对象(需求:项目中有一个全局的数据存储者,这个存储者只能有一个,不然会需要进行同步,增加复杂度)
function store() { this.store = { }; if (store.install) { return store.install; } store.install = this; } store.install = null; var s1 = new store(); var s2 = new store(); s1.store.a = 1; console.log(s2)- 源码实例:vue-router(vue-router必须保障全局有且只有一个,否则的话会错乱)
let _Vue; function install(Vue) { if (install.installed && _Vue === Vue) { return; } install.installed = true; _Vue = Vue; }
结构型
- 外观模式 -- 给你的一个套餐
- 享元模式 -- 共享来减少数量
- 适配器模式 -- 用适配代替更改
- 桥接模式 -- 独立出来,然后再对接过去
- 装饰者模式 -- 更优雅地扩展需求
提高可扩展性
提高可扩展性的目的
- 面对需求变更,方便需求更改;
- 减少代码修改的难度;
什么是好的可扩展性
- 需求的变更,不需要重写;
- 代码修改不会引起大规模变动;
- 方便加入新模块;
更好的更改代码
- 适配器模式
- 目标:接口
- 目的:通过写一个适配器,来代替替换
- 应用场景:面临接口不通用的问题
- 基本结构
var log = (function() { return window.console.log; })();- 示例:框架的变更(需求:目前项目使用的A框架,现在改成了B,两个框架十分类似,但是有少数几个方法不同)
A.c(); //b.css(); A.o(); //B.on(); window.A = B; A.c = function() { return B.css.apply(this, arguments); } A.o = function() { return B.on.apply(this, arguments); }- 示例:参数适配(需求:为了避免参数不适配产生问题,很多框架会有一个参数适配操作)
function f1(options) { var defaultOptions = { name: '', age: '', sex: '', color: 'red' }; for (let key in defaultOptions) { defaultOptions[key] = options[key] || defaultOptions[key]; } } - 装饰者模式
- 目标:方法作用
- 目的:不重写方法的扩展方法
- 应用场景:当一个方法需要扩展,但是又不好去修改方法
- 基本结构
var a = { b: function() { } } function myb() { a.b(); //TODO 要扩展的方法 }- 示例:扩展你的已有事件绑定(需求:现在项目改造,需要给input标签已经有的事件,增加一些操作)
dom.onclick = function() { console.log('1'); }; var decorator = function(dom, fn) { if (typeof dom.onclick == 'function') { var _old = dom.onclick; dom.onclick = function() { _old(); fn(); } } }; decorator(document.getElementById('dom1'), function() { console.log('还要执行2'); });- 示例:Vue的数组监听(需求:vue中利用defineProperty可以监听对象,那数组怎么办)
var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case 'push': case 'unshift': inserted = args; break case 'splice': inserted = args.slice(2); break } if (inserted) { ob.observeArray(inserted); } // notify change ob.dep.notify(); return result }); });
解耦你的方法与调用
- 命令模式
- 目的:解耦实现和调用,让双方互不干扰;
- 应用场景:调用的命令充满不确定性
- 基本结构
var command = (function() { var action = {}; return function excute() {} })();- 示例:绘图命令(需求:封装一系列的canvas绘图命令)
var canvasCommand = (function() { var actions = { drawCirecle: function() { }, drawRect: function() { } }; return function execute(commander) { if (Array.isArray(commander)) { commander.forEach(c => { let command = c.command; actions[command](c.config); }); } } })(); let commander = [ { command: 'drawCirecle', config: {} }, { command: 'drawCirecle', config: {} }, { command: 'drawRect', config: {} } ]; canvasCommand(commander);- 示例:绘制随机数量图片(需求:要做一个画廊,图片数量和排列顺序随机)
var createImg = (function() { var actions = { create: function(options) { var htmlArr = []; var _htmlstring = ''; var _htmlTemplate = "<div><img src='{{img-url}}'/></div><h2>{{title}}</h2>"; //策略模式 var displayWay = { normal: function(arr) { return []; }, reverse: function(arr) { return []; } }; options.imgArr.forEach(img => { var _html; _html = _htmlTemplate.replace('{{img-url}}', img.img).replace('{{title}}', img.title); htmlArr.push(_html); }); htmlArr = displayWay[options.type](htmlArr); _htmlstring = htmlArr.join(''); return "<div>" + _htmlstring + "</div>"; }, display: function(options) { let _html = this.create(options); options.target.innerHTML = _html; =764 }; return function execute(obj) { var _default = { imgArr: [{ img: 'xxx', title: 'img1' }], type: 'normal', target: document.body }; //适配器模式,适配参数 for (var key in _default) { _default[key] = obj[key] || _default[key]; } actions.display(_default); } })(); createImg({});
行为型
- 观察者模式 -- 我作为第三方转发
- 职责链模式 -- 像生产线一样组织模块
- 状态模式 -- 用状态代替判断
- 命令模式 -- 用命令去解耦
- 策略模式 -- 算法工厂
- 迭代器模式 -- 告别for循环
优化你的代码结构
- 策略模式/状态模式
- 目的:优化if-else分支
- 应用场景:当代码if-else分支过多时
- 基本结构(策略模式)
假设要编写一个计算器,有加减乘除,我们可以把一层一层的if判断,变成上面的形式function Strategy(type, a, b) { var strategyer = { add: function(a, b) { return a + b; }, minus: function(a, b) { return a - b; }, division: function(a, b) { return a / b; } } return strategyer[type](a, b); }- 基本结构(状态模式)为了减少if-else结构,将状态变成对象内部的一个状态,通过对象内部的状态改变,让其拥有不同行为
function stateFacotr(status) { var stateObject = { _status: '', state: { state1: function() { }, state2: function() { } }, run: function() { return this.state[this._status]; } } stateObject._status = status; return stateObject; } stateFacotr('state1').run();- 示例:(需求:项目有一个动态的内容,根据用户权限的不同显示不同的内容)
function showControll() { this.permission = ''; this.power = { boss: function() { console.log('boss权限') }, manager: function() { console.log('经理权限') }, staff: function() { console.log('普通员工权限') } } } showControll.prototype.show = function() { var _self = this; axios.get('xxx').then((res) => { _self.permission = res; _self.power[this.permission](); }); } new showControll().show();- 示例:复合运动(需求:有一个小球,可以控制它左右移动,或者左前,右前等方式移动)
function mover() { this.satus = []; this.actionHandle = { left: moveLeft, right: moveRight, top: moveTop, bottom: moveBottom } } mover.prototype.run = function() { this.status = Array.prototype.slice.call(arguments); this.status.forEach(action => { this.actionHandle[action](); }) } new mover().run('left', 'top'); - 外观模式
- 目的:通过为多个复杂的子系统提供一个一致的接口
- 应用场景:当完成一个操作,需要操作多个子系统,不如提供一个更高级的统一接口
- 基本结构:我们在组织方法模块时可以细化多个接口,但是我们给别人使用时,要合为一个接口,就像你可以直接去餐厅点套餐
function Model1() { } function Model2() { } //功能由Model1获取Model2的结果来完成 function use() { Model2(Model1()); }- 示例:插件封装的规律(需求:插件基本上都会给最终使用提供一个高级接口)
- 示例:封装成方法的思想(需求:在兼容时代,我们会常常需要检测能力,不妨作为一个统一接口)
function 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; } } - 迭代器模式
- 目的:不访问内部的情况下,方便的遍历数据
- 应用场景:当我们要对某个对象进行操作,但是又不能暴露内部
- 基本结构:在不暴露对象内部结构的同时,可以顺序的访问对象内部的,可以帮助我们简化循环,简化数据操作
function Iterator(item) { this.item = item; } Iterator.prototype.dealEach = function(fn) { for (var i = 0; i < this.item.length; i++) { fn(this.item[i], i); } }- 示例:典型的forEach就是迭代器(构建一个自己的forEach,循环数组和对象)
function Iterator(data) { this.data = data; } Iterator.prototype.dealEach = function(fn) { if (this.data instanceof Array) { for (let i = 0; i < this.data.length; i++) { fn(this.data[i], i); } } else { for (let key in this.data) { fn(this.data[key], key); } } }- 示例:给你的项目数据添加迭代器(需求:项目会经常对于后端数据进行遍历操作,不如封装一个迭代器,遍历的更方便)
var data = [ { num: 1 }, { num: 2 }, { num: 3 } ]; function iteratorFactory(data) { function Iterator(data) { this.data = data; } Iterator.prototype.geHasSomenum = function(handler, num) { var _arr = []; var handleFn; if (typeof handler == 'function') { handleFn = handler; } else { handleFn = function(item) { if (item[handler] === num) { return item; } } } for (let i = 0; i < this.data.length; i++) { var _result = handlerFn.call(this, this.data[i]); if (_result) { _arr.push(_result); } } return _arr; } } iteratorFactory(data).geHasSomenum('num', 1); iteratorFactory(data).geHasSomenum(function(item) { if (item.num - 1 == 2) { return item; } }); - 备忘录模式
- 目的:记录状态,方便回滚
- 应用场景:系统状态多样,为了保证状态的回滚方便,记录状态
- 基本结构:记录对象内部的状态,当有需要时回滚到之前的状态或者方便对象使用
function Memento() { var cache = {}; return function(cacheName) { if (cache[cacheName]) { //有缓存的操作的 } else { //没缓存的操作 } } } var MementoFn = Memento(); MementoFn('xxx');- 示例:文章页缓存(需求:项目有一个文章页需求,现在进行优化,如果上一篇已经读取过了,则不进行请求,否则请求文章数据)
function pager() { var cache = {}; return function(pageName) { if (cache[pageName]) { return cache[pageName]; } else { axios.get(options).then(res => { cache[pageName] = res; }) } } }- 示例:前进后退功能(需求:开发一个可移动的div,拥有前进后退功能回滚到之前的位置)
function moveDiv() { this.sateList = []; this.nowState = 0; } moveDiv.prototype.move = function(type, num) { changeDiv(type, num); this.sateList.push({ type: type, num: num }); this.nowState = this.sateList.length - 1; } move.prototype.go = function() { var _state; if (this.nowSate < this.sateList.length - 1) { this.nowState++; _state = this.sateList[this.nowState]; changeDiv(_state.type, _state.num); } }
技巧型
- 链模式 -- 链式调用
- 惰性模式 -- 我就要搞机器学习
- 委托模式 -- 让别人代替你收快递
- 等待者模式 -- 等你们都回来再吃饭
- 数据访问模式 -- 一个方便的数据管理器