时间:2023-09-04 05:42:01 | 来源:网站运营
时间:2023-09-04 05:42:01 来源:网站运营
JavaScript中常见的十五种设计模式(上):在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式学习设计模式,有助于写出可复用和可维护性高的程序
function SetManager(name) { this.manager = name;}SetManager.prototype.getName = function() { console.log(this.manager);};var SingletonSetManager = (function() { var manager = null; return function(name) { if (!manager) { manager = new SetManager(name); } return manager; } })();SingletonSetManager('a').getName(); // aSingletonSetManager('b').getName(); // aSingletonSetManager('c').getName(); // a
这是比较简单的做法,但是假如我们还要设置一个HR呢?就得复制一遍代码了// 提取出通用的单例function getSingleton(fn) { var instance = null; return function() { if (!instance) { instance = fn.apply(this, arguments); } return instance; }}
再进行调用,结果还是一样// 获取单例var managerSingleton = getSingleton(function(name) { var manager = new SetManager(name); return manager;});managerSingleton('a').getName(); // amanagerSingleton('b').getName(); // amanagerSingleton('c').getName(); // a
这时,我们添加HR时,就不需要更改获取单例内部的实现了,仅需要实现添加HR所需要做的,再调用即可function SetHr(name) { this.hr = name;}SetHr.prototype.getName = function() { console.log(this.hr);};var hrSingleton = getSingleton(function(name) { var hr = new SetHr(name); return hr;});hrSingleton('aa').getName(); // aahrSingleton('bb').getName(); // aahrSingleton('cc').getName(); // aa
或者,仅想要创建一个div层,不需要将对象实例化,直接调用函数function createPopup(html) { var div = document.createElement('div'); div.innerHTML = html; document.body.append(div); return div;}var popupSingleton = getSingleton(function() { var div = createPopup.apply(this, arguments); return div;});console.log( popupSingleton('aaa').innerHTML, popupSingleton('bbb').innerHTML, popupSingleton('bbb').innerHTML); // aaa aaa aaa
// 加权映射关系var levelMap = { S: 10, A: 8, B: 6, C: 4};// 组策略var scoreLevel = { basicScore: 80, S: function() { return this.basicScore + levelMap['S']; }, A: function() { return this.basicScore + levelMap['A']; }, B: function() { return this.basicScore + levelMap['B']; }, C: function() { return this.basicScore + levelMap['C']; }}// 调用function getScore(level) { return scoreLevel[level] ? scoreLevel[level]() : 0;}console.log( getScore('S'), getScore('A'), getScore('B'), getScore('C'), getScore('D')); // 90 88 86 84 0
在组合业务规则方面,比较经典的是表单的验证方法。这里列出比较关键的部分// 错误提示var errorMsgs = { default: '输入数据格式不正确', minLength: '输入数据长度不足', isNumber: '请输入数字', required: '内容不为空'};// 规则集var rules = { minLength: function(value, length, errorMsg) { if (value.length < length) { return errorMsg || errorMsgs['minLength'] } }, isNumber: function(value, errorMsg) { if (!//d+/.test(value)) { return errorMsg || errorMsgs['isNumber']; } }, required: function(value, errorMsg) { if (value === '') { return errorMsg || errorMsgs['required']; } }};// 校验器function Validator() { this.items = [];};Validator.prototype = { constructor: Validator, // 添加校验规则 add: function(value, rule, errorMsg) { var arg = [value]; if (rule.indexOf('minLength') !== -1) { var temp = rule.split(':'); arg.push(temp[1]); rule = temp[0]; } arg.push(errorMsg); this.items.push(function() { // 进行校验 return rules[rule].apply(this, arg); }); }, // 开始校验 start: function() { for (var i = 0; i < this.items.length; ++i) { var ret = this.items[i](); if (ret) { console.log(ret); // return ret; } } }};// 测试数据function testTel(val) { return val;}var validate = new Validator();validate.add(testTel('ccc'), 'isNumber', '只能为数字'); // 只能为数字validate.add(testTel(''), 'required'); // 内容不为空validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位validate.add(testTel('12345'), 'minLength:5', '最少5位');var ret = validate.start();console.log(ret);
4. 优缺点// 主体,发送消息function sendMsg(msg) { console.log(msg);}// 代理,对消息进行过滤function proxySendMsg(msg) { // 无消息则直接返回 if (typeof msg === 'undefined') { console.log('deny'); return; } // 有消息则进行过滤 msg = ('' + msg).replace(/泥/s*煤/g, ''); sendMsg(msg);}sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀proxySendMsg('泥煤呀泥 煤'); // 呀proxySendMsg(); // deny
它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这数据保护代理的形式// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理function debounce(fn, delay) { delay = delay || 200; var timer = null; return function() { var arg = arguments; // 每次操作时,清除上次的定时器 clearTimeout(timer); timer = null; // 定义新的定时器,一段时间后进行操作 timer = setTimeout(function() { fn.apply(this, arg); }, delay); }};var count = 0;// 主体function scrollHandle(e) { console.log(e.type, ++count); // scroll}// 代理var proxyScrollHandle = (function() { return debounce(scrollHandle, 500);})();window.onscroll = proxyScrollHandle;
缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率// 主体function add() { var arg = [].slice.call(arguments); return arg.reduce(function(a, b) { return a + b; });}// 代理var proxyAdd = (function() { var cache = []; return function() { var arg = [].slice.call(arguments).join(','); // 如果有,则直接从缓存返回 if (cache[arg]) { return cache[arg]; } else { var ret = add.apply(this, arguments); return ret; } };})();console.log( add(1, 2, 3, 4), add(1, 2, 3, 4), proxyAdd(10, 20, 30, 40), proxyAdd(10, 20, 30, 40)); // 10 10 100 100
[1, 2, 3].forEach(function(item, index, arr) { console.log(item, index, arr);});
不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码function each(obj, cb) { var value; if (Array.isArray(obj)) { for (var i = 0; i < obj.length; ++i) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } } else { for (var i in obj) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } }}each([1, 2, 3], function(index, value) { console.log(index, value);});each({a: 1, b: 2}, function(index, value) { console.log(index, value);});// 0 1// 1 2// 2 3// a 1// b 2
再来看一个例子,强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句function getManager() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } else if (year >= 2100) { console.log('C'); } else { console.log('B'); }}getManager(); // B
将每个条件语句拆分出逻辑函数,放入迭代器中迭代function year2000() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } return false;}function year2100() { var year = new Date().getFullYear(); if (year >= 2100) { console.log('C'); } return false;}function year() { var year = new Date().getFullYear(); if (year > 2000 && year < 2100) { console.log('B'); } return false;}function iteratorYear() { for (var i = 0; i < arguments.length; ++i) { var ret = arguments[i](); if (ret !== false) { return ret; } }}var manager = iteratorYear(year2000, year2100, year); // B
// 订阅document.body.addEventListener('click', function() { console.log('click1');}, false);document.body.addEventListener('click', function() { console.log('click2');}, false);// 发布document.body.click(); // click1 click2
自己实现一下// 观察者var observer = { // 订阅集合 subscribes: [], // 订阅 subscribe: function(type, fn) { if (!this.subscribes[type]) { this.subscribes[type] = []; } // 收集订阅者的处理 typeof fn === 'function' && this.subscribes[type].push(fn); }, // 发布 可能会携带一些信息发布出去 publish: function() { var type = [].shift.call(arguments), fns = this.subscribes[type]; // 不存在的订阅类型,以及订阅时未传入处理回调的 if (!fns || !fns.length) { return; } // 挨个处理调用 for (var i = 0; i < fns.length; ++i) { fns[i].apply(this, arguments); } }, // 删除订阅 remove: function(type, fn) { // 删除全部 if (typeof type === 'undefined') { this.subscribes = []; return; } var fns = this.subscribes[type]; // 不存在的订阅类型,以及订阅时未传入处理回调的 if (!fns || !fns.length) { return; } if (typeof fn === 'undefined') { fns.length = 0; return; } // 挨个处理删除 for (var i = 0; i < fns.length; ++i) { if (fns[i] === fn) { fns.splice(i, 1); } } }};// 订阅岗位列表function jobListForA(jobs) { console.log('A', jobs);}function jobListForB(jobs) { console.log('B', jobs);}// A订阅了笔试成绩observer.subscribe('job', jobListForA);// B订阅了笔试成绩observer.subscribe('job', jobListForB);// A订阅了笔试成绩observer.subscribe('examinationA', function(score) { console.log(score);});// B订阅了笔试成绩observer.subscribe('examinationB', function(score) { console.log(score);});// A订阅了面试结果observer.subscribe('interviewA', function(result) { console.log(result);});observer.publish('examinationA', 100); // 100observer.publish('examinationB', 80); // 80observer.publish('interviewA', '备用'); // 备用observer.publish('job', ['前端', '后端', '测试']); // 输出A和B的岗位// B取消订阅了笔试成绩observer.remove('examinationB');// A都取消订阅了岗位observer.remove('job', jobListForA);observer.publish('examinationB', 80); // 没有可匹配的订阅,无输出observer.publish('job', ['前端', '后端', '测试']); // 输出B的岗位
4. 优缺点var incrementCommand = { execute: function() { // something }};
不过接下来的例子是一个自增命令,提供执行、撤销、重做功能// 自增function IncrementCommand() { // 当前值 this.val = 0; // 命令栈 this.stack = []; // 栈指针位置 this.stackPosition = -1;};IncrementCommand.prototype = { constructor: IncrementCommand, // 执行 execute: function() { this._clearRedo(); // 定义执行的处理 var command = function() { this.val += 2; }.bind(this); // 执行并缓存起来 command(); this.stack.push(command); this.stackPosition++; this.getValue(); }, canUndo: function() { return this.stackPosition >= 0; }, canRedo: function() { return this.stackPosition < this.stack.length - 1; }, // 撤销 undo: function() { if (!this.canUndo()) { return; } this.stackPosition--; // 命令的撤销,与执行的处理相反 var command = function() { this.val -= 2; }.bind(this); // 撤销后不需要缓存 command(); this.getValue(); }, // 重做 redo: function() { if (!this.canRedo()) { return; } // 执行栈顶的命令 this.stack[++this.stackPosition](); this.getValue(); }, // 在执行时,已经撤销的部分不能再重做 _clearRedo: function() { this.stack = this.stack.slice(0, this.stackPosition + 1); }, // 获取当前值 getValue: function() { console.log(this.val); }};
再实例化进行测试,模拟执行、撤销、重做的操作var incrementCommand = new IncrementCommand();// 模拟事件触发,执行命令var eventTrigger = { // 某个事件的处理中,直接调用命令的处理方法 increment: function() { incrementCommand.execute(); }, incrementUndo: function() { incrementCommand.undo(); }, incrementRedo: function() { incrementCommand.redo(); }};eventTrigger['increment'](); // 2eventTrigger['increment'](); // 4eventTrigger['incrementUndo'](); // 2eventTrigger['increment'](); // 4eventTrigger['incrementUndo'](); // 2eventTrigger['incrementUndo'](); // 0eventTrigger['incrementUndo'](); // 无输出eventTrigger['incrementRedo'](); // 2eventTrigger['incrementRedo'](); // 4eventTrigger['incrementRedo'](); // 无输出eventTrigger['increment'](); // 6
此外,还可以实现简单的宏命令(一系列命令的集合)var MacroCommand = { commands: [], add: function(command) { this.commands.push(command); return this; }, remove: function(command) { if (!command) { this.commands = []; return; } for (var i = 0; i < this.commands.length; ++i) { if (this.commands[i] === command) { this.commands.splice(i, 1); } } }, execute: function() { for (var i = 0; i < this.commands.length; ++i) { this.commands[i].execute(); } }};var showTime = { execute: function() { console.log('time'); }};var showName = { execute: function() { console.log('name'); }};var showAge = { execute: function() { console.log('age'); }};MacroCommand.add(showTime).add(showName).add(showAge);MacroCommand.remove(showName);MacroCommand.execute(); // time age
// 文件夹 组合对象function Folder(name) { this.name = name; this.parent = null; this.files = [];}Folder.prototype = { constructor: Folder, add: function(file) { file.parent = this; this.files.push(file); return this; }, scan: function() { // 委托给叶对象处理 for (var i = 0; i < this.files.length; ++i) { this.files[i].scan(); } }, remove: function(file) { if (typeof file === 'undefined') { this.files = []; return; } for (var i = 0; i < this.files.length; ++i) { if (this.files[i] === file) { this.files.splice(i, 1); } } }};// 文件 叶对象function File(name) { this.name = name; this.parent = null;}File.prototype = { constructor: File, add: function() { console.log('文件里面不能添加文件'); }, scan: function() { var name = [this.name]; var parent = this.parent; while (parent) { name.unshift(parent.name); parent = parent.parent; } console.log(name.join(' / ')); }};
构造好组合对象与叶对象的关系后,实例化,在组合对象中插入组合或叶对象var web = new Folder('Web');var fe = new Folder('前端');var css = new Folder('CSS');var js = new Folder('js');var rd = new Folder('后端');web.add(fe).add(rd);var file1 = new File('HTML权威指南.pdf');var file2 = new File('CSS权威指南.pdf');var file3 = new File('JavaScript权威指南.pdf');var file4 = new File('MySQL基础.pdf');var file5 = new File('Web安全.pdf');var file6 = new File('Linux菜鸟.pdf');css.add(file2);fe.add(file1).add(file3).add(css).add(js);rd.add(file4).add(file5);web.add(file6);rd.remove(file4);// 扫描web.scan();
扫描结果为// 体育运动function Sport() {}Sport.prototype = { constructor: Sport, // 模板,按顺序执行 init: function() { this.stretch(); this.jog(); this.deepBreath(); this.start(); var free = this.end(); // 运动后还有空的话,就拉伸一下 if (free !== false) { this.stretch(); } }, // 拉伸 stretch: function() { console.log('拉伸'); }, // 慢跑 jog: function() { console.log('慢跑'); }, // 深呼吸 deepBreath: function() { console.log('深呼吸'); }, // 开始运动 start: function() { throw new Error('子类必须重写此方法'); }, // 结束运动 end: function() { console.log('运动结束'); }};// 篮球function Basketball() {}Basketball.prototype = new Sport();// 重写相关的方法Basketball.prototype.start = function() { console.log('先投上几个三分');};Basketball.prototype.end = function() { console.log('运动结束了,有事先走一步'); return false;};// 马拉松function Marathon() {}Marathon.prototype = new Sport();var basketball = new Basketball();var marathon = new Marathon();// 子类调用,最终会按照父类定义的顺序执行basketball.init();marathon.init();
关键词:设计,模式,中常