可以看到这里if...else看得我们头都大了,还是用策略模式来优化下吧:
// 建一个移动控制类 function MoveController() { this.status = []; this.moveHanders = { // 写上每个指令对应的方法 up: moveUp, dowm: moveDown, left: moveLeft, right: moveRight } } // MoveController添加一个实例方法来触发运动 MoveController.prototype.run = function(...args) { this.status = args; this.status.forEach((move) => { this.moveHanders[move](); }); } // 使用时 new MoveController().run('left', 'up')上述代码我们也是将所有的策略都封装到了moveHanders里面,然后通过实例方法run传入的方法来执行具体的策略。
外观模式 基本结构当我们设计一个模块时,里面的方法可以会设计得比较细,但是暴露给外面使用的时候,不一定非得直接暴露这些细小的接口,外部使用者需要的可能是组合部分接口来实现某个功能,我们暴露的时候其实就可以将这个组织好。这就像餐厅里面的菜单,有很多菜,用户可以一个一个菜去点,也可以直接点一个套餐,外观模式提供的就类似于这样一个组织好的套餐:
function model1() {} function model2() {} // 可以提供一个更高阶的接口,组合好了model1和model2给外部使用 function use() { model2(model1()); } 实例:常见的接口封装外观模式说起来其实非常常见,很多模块内部都很复杂,但是对外的接口可能都是一两个,我们无需知道复杂的内部细节,只需要调用统一的高级接口就行,比如下面的选项卡模块:
// 一个选项卡类,他内部可能有多个子模块 function Tab() {} Tab.prototype.renderHTML = function() {} // 渲染页面的子模块 Tab.prototype.bindEvent = function() {} // 绑定事件的子模块 Tab.prototype.loadCss = function() {} // 加载样式的子模块 // 对外不需要暴露上面那些具体的子模块,只需要一个高级接口就行 Tab.prototype.init = function(config) { this.loadCss(); this.renderHTML(); this.bindEvent(); }上述代码这种封装模式非常常见,其实也是用到了外观模式,他当然也可以暴露具体的renderHTML,bindEvent,loadCss这些子模块,但是外部使用者可能并不关心这些细节,只需要给一个统一的高级接口就行,就相当于改变了外观暴露出来,所以叫外观模式。
实例:方法的封装这个例子也很常见,就是把一些类似的功能封装成一个方法,而不是每个地方去写一遍。在以前还是IE主导天下的时候,我们需要做很多兼容的工作,仅仅是一个绑定事件就有addEventListener,attachEvent,onclick等,为了避免每次都进行这些检测,我们可以将他们封装成一个方法:
function addEvent(dom, type, fn) { if(dom.addEventListener) { return dom.addEventListener(type, fn, false); } else if(dom.attachEvent) { return dom.attachEvent("on" + type, fn); } else { dom["on" + type] = fn; } }然后将addEvent暴露出去给外面使用,其实我们在实际编码时经常这样封装方法,只是我们自己可能没意识到这个是外观模式。
迭代器模式 基本结构迭代器模式模式在JS里面很常见了,数组自带的forEach就是迭代器模式的一个应用,我们也可以实现一个类似的功能:
function Iterator(items) { this.items = items; } Iterator.prototype.dealEach = function(fn) { for(let i = 0; i < this.items.length; i++) { fn(this.items[i], i); } }上述代码我们新建了一个迭代器类,构造函数接收一个数组,实例方法dealEach可以接收一个回调,对实例上的items每一项都执行这个回调。
实例:数据迭代器其实JS数组很多原生方法都用了迭代器模式,比如find,find接收一个测试函数,返回符合这个测试函数的第一个数据。这个例子要做的是扩展这个功能,返回所有符合这个测试函数的数据项,而且也可以接收两个参数,第一个参数是属性名,第二个参数是值,同样返回所有该属性与值匹配的项:
// 外层用一个工厂模式封装下,调用时不用写new function iteratorFactory(data) { function Iterator(data) { this.data = data; } Iterator.prototype.findAll = function(handler, value) { const result = []; let handlerFn; // 处理参数,如果第一个参数是函数,直接拿来用 // 如果不是函数,就是属性名,给一个对比的默认函数 if(typeof handler === 'function') { handlerFn = handler; } else { handlerFn = function(item) { if(item[handler] === value) { return true; } return false; } } // 循环数据里面的每一项,将符合结果的塞入结果数组 for(let i = 0; i < this.data.length; i++) { const item = this.data[i]; const res = handlerFn(item); if(res) { result.push(item) } } return result; } return new Iterator(data); } // 写个数据测试下 const data = [{num: 1}, {num: 2}, {num: 3}]; iteratorFactory(data).findAll('num', 2); // [{num: 2}] iteratorFactory(data).findAll(item => item.num >= 2); // [{num: 2}, {num: 3}]上述代码封装了一个类似数组find的迭代器,扩展了他的功能,这种迭代器非常适合用来处理API返回的大量结构相似的数据。
备忘录模式 基本结构