单例模式
- 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建
- 最常见的单例模式,把业务逻辑和判断耦合在一起,如果业务逻辑变化不大的话使用
- 以登录组件框为例:
var createLoginLayer = (function (){}
var single = null;
return function(){
if(single){
return single;
}
var single = document.getElementById('div');
single.innerHTML = 'login';
document.body.appendChild(single);
return single;
})()
// 使用:var layer = createLoginLayer();
- 将业务逻辑和单例判断分离,只要变换传入函数即可改变业务逻辑
- ```js
var getInstance = function(fn){
var result = null;
return result || (result = fn.apply(this, arguments));
}
function createLoginLayer(){
var div = document.getElementById('div');
div.innerHTML = 'login';
document.body.appendChild(div);
return div;
}
//使用 var layer = getInstance(createLoginLayer)
策略模式
策略模式可以消除代码中大片的条件分支语句
案例代码:计算奖金
var calculateBonus = function(level, salary) { if (level === 'S') { return salary * 4; } else if(level === 'A') { return salary * 3; } else if (level === 'B') { return salary * 2; };
}
calculateBonus('S', 11500)
- 问题:如果要改变奖金倍数等要深入函数内部修改
- 组合函数重构代码:
- ``` js
var calculateBonus = function(level, salary) {
if (level === 'S') {
levelS();
} else if(level === 'A') {
levelA();
} else if (level === 'B') {
levelB();
};
}
function levelS() {
return salary * 4;
}
function levelA() {
return salary * 3;
}
function levelB() {
return salary * 2;
}
组合函数的问题在于扩展奖金等级时还要改变calculateBonus函数
使用策略模式重构代码
模仿传统面向对象语言中的实现,把每种绩效的计算规则都封装在对应的策略类里面
function Bonus() {
this.strategy = null;
this.salary = null;
}
Bonus.prototype.setStrategy = function(strategy) {
this.strategy = strategy;
}
Bonus.prototype.setSalary = function(salary) {
this.salary = salary;
}
Bonus.prototype.getBonus = function() {
return this.strategy.calculate(this.salary);
}
function levelS() {
}
levelS.prototype.calculate = function(salary) {
return salary * 4;
}
function levelA() {
}
levelA.prototype.calculate = function(salary) {
return salary * 3;
}
function levelB() {
}
levelB.prototype.calculate = function(salary) {
return salary * 2;
}
// 使用
var bonus = new Bonus();
bonus.getStrategy(new levelB());
- js 版本策略模式
- ```js
var strategies = {
}
strategies.S = function(salary) {
return salary * 4;
}
strategies.A = function(salary) {
return salary * 3;
}
strategies.B = function(salary) {
return salary * 2;
}
var calculateBonus = function(level, salary) {
return strategies[level](salary);
}
calculateBonus('S', 11500);
- 策略模式的运用 -- 缓动动画
- 缓动动画是通过连续改变元素的某个css属性,left top等,这里会出现多个条件分支判断,所以可使用策略模式,缓动动画有多个运动形式,也会出现多个分支判断
//动画函数库
var teen = {
linear: function(t, b, c, d) {
return (t / d) * c + b;
},
easeIn: function(t, b, c, d) {
return (t /= d ) * c * t + b;
},
strongEaseIn: function(t, b, c, d) {
return c * (t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d) {
return c * ( (t = t / d - 1) * t * t * t * t + 1)+ b;
},
sineaseIn: function(t, b, c, d) {
return c * (t /= d ) * t * t + b;
},
sineaseOut: function(t, b, c, d) {
return c * ( (t = t / d - 1) * t * t + 1) + b;
}
}
var Animation = function(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
}
Animation.prototype.start = function( propertyName, endPos, duration, easing) {
this.startTime = +new Date;
this.startPos = this.dom.getBoundingClientRect()[propertyName];
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
this.easing = teen[easing];
var timerId = setInterval(function() {
if (this.step() === false) {
clearInterval(timerId);
};
}.bind(this), 19)
}
//负责计算函数位置和调用更新css属性值的方法
Animation.prototype.step = function() {
var t = +new Date;
if (t >= this.startTime + this.duration) {
this.update( this.endPos );
return false;
};
var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
this.update(pos);
}
Animation.prototype.update = function(pos) {
this.dom.style[this.propertyName] = pos + 'px';
}
- ```html
- 利用策略模式重构表单校验代码
- 案例代码:
var registerForm = document.getElementBy('registerForm');
registerForm.onsubmit = function() {
if ( registerForm.userName.value === '' ) {
alert('用户名不能为空');
return false;
};
if ( registerForm.password.value.length === '' ) {
alert('密码长度不能少于6位');
return false;
};
if (!/(^1[3|5|8][0-9]{9}$)/).test(registerForm.phoneNumber.value)) {
alert('手机号码不正确');
return false;
};
}
- 问题: 多个if else, 函数缺乏扩展性,复用性差
- 用策略模式重构表单提交代码
- ```js
var strategies = {
isNonEmpty: function( value, errMsg) {
if (value === '') {
return errMsg
};
},
minLength: function(value, length, errMsg) {
if (value.length < length) {
return errMsg
};
},
isMobile: function(value, errMsg) {
if(!(/^1[3|5|8][0-9]{9}$/.test(value)) ) {
return errMsg;
}
}
}
var Validation = function() {
this.cache = [];
}
Validation.prototype.start = function() {
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
var errMsg = validatorFunc;();
if (errMsg) {
return errMsg;
};
}
}
Validation.prototype.add = function(dom, rules) {
var self = this;
for(var i = 0, rule; rule = rules[i++];) {
var strategyAry = rule.strategy.split(':');
var errMsg = rule.errMsg;
this.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errMsg);
return strategies[strategy].apply(dom, strategyAry);
});
}
}
## 代理模式
- *保护代理*:代理可以帮助对象过滤一些请求,用于控制不同权限的对象对目标对象的访问
- *虚拟代理*:把一些开销很大的对象延迟到真正需要它的时候才去创建
- *缓存代理*:为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传进参数跟之前的一致,则可以直接返回前面存储的结果。
- *代理的意义*:
- 单一原则:指的是就一个类而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时面设计可能会遭到意外的破坏
### 1.虚拟代理的案例代码 -- 实现图片预加载
- 在图片加载完毕之前使用loading图片代替
- ```js
var myImage = (function(){
var imgNode = document.createElement('img');
document.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
var proxyImage = (function() {
var img = new Image();
img.onload = function() {
myImage.setSrc(this.src);
}
return {
setSrc: function(src) {
myImage.setSrc('loading.gif');
img.src = src;
}
}
})();
proxyImage.setSrc('other.png');
2.虚拟代理的案例代码 -- 合并http请求
- 用虚拟代理改变点击checkbox一次发送一次请求的案例
### 缓存代理 -- 缓存求乘积的函数
- ``` js
var mult = function() {
var a = 1;
for(var i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
}
var proxyMult = (function(){
var cache = {};
return function() {
var args = [].join.call([], arguments);
if (cache[args]) {
return cache[args];
};
return cache[args] = mult.apply(this, arguments)
}
})();
//使用,第二次直接返回缓存
proxyMult(1,2,3,4)
proxyMult(1,2,3,4)
缓存代理 -- 高阶函数动态代理
var mult = function() {
var a = 1;
for(var i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
}
var plus = function() {
var a = 0;
for(var i = 0; i < arguments.length; i++) {
a = a + arguments[i];
}
return a;
}
var proxyFactory = (function(){
var cache = {};
return function(fn) {
var args = [].join.call([], arguments);
if (cache[args]) {
return cache[args];
};
return cache[args] = fn.apply(this, arguments)
}
})();
//使用
var proxyMult = proxyFactory(mult);
var proxyPlus = proxyFactory(plus);
## 迭代器模式
- 迭代器模式是指提供一种方法顺序访问一个聚合对象中的各种元素,而又不需要暴露该对象的内部表示。
- 模式jq实现each
- ```js
function each(ary, fn) {
for(var i = 0; i < ary.length; i++) {
fn.call(ary[i], i, ary[i]);
}
}
- 加入中止条件的迭代器
function each(ary, fn) {
for(var i = 0; i < ary.length; i++) {
if( fn.call(ary[i], ary[i], i) === false) {
break;
}
}
}
//使用
each([1,2,3], function(value, i) {
if (value > 2) {
return false;
};
console.log(value)
});
- 迭代器可以分为内部迭代器和外部迭代器
- 上面的实现是内部迭代器,内部迭代器调用非常方便,外界不用关心迭代器内部的实现,跟迭代器的交互仅是一次初始调用,这也是内部迭代器的缺点,由于内部迭代器的迭代规则已经被规定,上面的函数无法同时迭代两个数组
- 外部迭代器,比较两个数组等
- ```js
var Iterator = function(obj) {
var current = 0;
var next = function() {
current += 1;
}
var isDone = function() {
return obj.length <= current;
}
var getItem = function() {
return obj[current];
}
return {
next: next,
isDone: isDone,
getItem: getItem
}
}
function compare(iterator1, iterator2s) {
while(!iterator2.isDone() && !iterator1.isDone()) {
if( iterator1.getItem() !== iterator2.getItem() ) {
console.log('不相等');
break;
}
console.log(iterator1.getItem())
iterator1.next();
iterator2.next();
}
}
var iterator1 = Iterator([2,3,4,5]);
var iterator2 = Iterator([2,3,3,5]);
compare(iterator1, iterator2);
- 迭代器应用举例
- 根据不同浏览器获取相应的上传组件对象
- 重构前示例代码:多个条件分支,扩展性差
var getUploadObj = function() { try { return new ActiveObject('TXFTNActiveX.FTNUpload'); } catch (e) { if (supportFlash()) { var str = ''; return $('str').appendTo($('body')); } else { var str = ''; return $('str').appendTo($('body')); } }
- 用迭代器模式重构后
- ```js
function getActiveUploadObj() {
try {
return new ActiveObject('TXFTNActiveX.FTNUpload');
} catch (e) {
return false;
}
}
function getFlashUploadObj() {
if (supportFlash()) {
var str = '';
return $('str').appendTo($('body'));
}
return false;
}
function getFormUploadObj() {
var str = '';
return $('str').appendTo($('body'));
}
var iteratorUpLoadObj = function() {
var uploadObj = null;
for(var i = 0; i < arguments.length; i++) {
uploadObj = arguments[i]();
if (uploadObj !=== false) {
return uploadObj;
};
}
}
var uploadObj = iteratorUpLoadObj(getActiveUploadObj, getFormUploadObj, getFormUploadObj);
发布与订阅模式 -- 观察者模式
- 定义对象间一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都将得到通知
- 自定义事件 -- 最简单的观察者模式
var Event = {
list: [],
listen: function(fn) {
this.list.push(fn);
},
trigger: function() {
for(var i = 0; i < this.list.length; i++) {
this.list[i].apply(this, arguments);
}
}
}
Event.listen(function() {
console.log('first');
console.log(arguments);
});
Event.listen(function() {
console.log('second');
console.log(arguments);
});
Event.trigger('sss','ddd');
Event.trigger('sss');
- 改进版: 指定key订阅
- ```js
var Event = {
list: {},
listen: function(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
};
this.list[key].push(fn);
},
trigger: function() {
var key = [].shift.call(arguments);
if (!this.list || !this.list[key]) {
return;
};
for(var i = 0; i < this.list[key].length; i++) {
this.list[key][i].apply(this, arguments);
}
}
}
Event.listen('first', function() {
console.log(arguments);
});
Event.listen('second', function() {
console.log(arguments);
});
Event.trigger('first', 'sss','ddd');
Event.trigger('second','sss');
- 给所有对象都动态安装订阅-发布模式的函数
var newObj = Object.create(Event);
newObj.listen('newObj', function() {
console.log(arguments);
});
newObj.trigger('newObj', 'sss','ddd');
- 增加取消订阅事件
- ```js
Event.remove = function(key, fn) {
var fns = this.list[key];
if (!fns) {
return false;
};
if (!fn) {
fns.length = 0;
} else {
for(var i = 0; i < fns.length; i++) {
if (fn === fns[i]) {
fns.splice(i, 1);
};
}
}
}
- 以上模式如果是先发布后订阅则收不到消息
- 事件命名也有可能重复冲突
// 先发布后订阅的使用
Event.trigger('click', 1);
Event.listen('click', function(a) {
console.log(a);
});
//使用命名空间
Event.create('nameSpace1').listen(function(a) {
console.log(a);
});
Event.create('nameSpace1').trigger('click', 1);
Event.create('nameSpace2').listen(function(a) {
console.log(a);
});
Event.create('nameSpace2').trigger('click', 2);
var Event = (function(){
var global = this,
Event,
_default = 'default';
Event = function() {
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function(ary, fn) {
var ret;
for(var i = 0, l = ary.length; i < l; i++) {
var n = ary[i];
ret = fn.call(n, i, n)
}
return ret;
}
_listen = function(key, fn, cache) {
if (!cache[key]) {
cache[key] = [];
};
cache[key].push(fn);
}
_remove = function(key, cache, fn) {
if (cache[key]) {
if (fn) {
for(var i = 0; i < cache[key].length; i++) {
if( cache[key][i] == fn) {
cache[key].splice(i, 1);
}
}
} else {
cache[key] = [];
}
};
}
_trigger = function() {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if (!stack || !stack.length) {
return;
};
return each(stack, function() {
return this.apply(_self, args);
});
}
_create = function(namespace) {
var namespace = namespace || _default;
var cache = {},
offlineStack = [],
ret = {
listen: function(key, fn, last) {
_listen(key, fn, cache);
if (offlineStack === null) {
return;
};
if (last === 'last') {
offlineStack.length && offlineStack.pop()();
} else {
each(offlineStack, function() {
this();
});
}
offlineStack = null;
},
one: function(key, fn, last) {
_remove(key, cache);
this.listen(key, fn, last);
},
remove: function(key, fn) {
_remove(key, cache, fn);
},
trigger: function() {
var fn,
args,
_self = this,
_unshift.call(arguments, cache);
fn = function() {
return _trigger.apply(_self, args);
}
if (offlineStack) {
return offlineStack.push(fn);
};
return fn();
}
}
return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret;
}
return {
create: _create,
one: function(key, fn, last) {
var event = this.create();
event.on(key, fn, last);
},
remove: function() {
var event = this.create();
event.remove(key, fn);
},
listen: function(key, fn, last) {
var event = this.create();
event.listen(key, fn, last);
},
trigger: function() {
var event = this.create();
event.trigger.apply(this, arguments);
}
}
}
})();
## 命令模式
- 案例背景:有数十个button按钮的用户界面,一个人负责绘制这些按钮,一个人负责编写点击后的具体行为,两人约定点击按钮时会执行回调函数
- ```js
var setCommand = function(button, func) {
button.onclick = function() {
func();
}
}
- 定义菜单和子菜单两个对象以及它们的功能
var MenuBar = {
refresh: function() {
console.log('refresh')
}
}
var SubMenu = {
add: function() {
console.log('add sub')
},
del: function() {
console.log('del')
}
}
- 把行为封装在命令类中
- ```js
var RefreshMenuBarCommand = function(receiver) {
return function(){
receiver.refresh();
};
}
var AddSubMenuCommand = function(receiver) {
return function(){
receiver.add();
};
}
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
var addSubMenuCommand = AddSubMenuCommand(MenuBar);
setCommand(button1, addSubMenuCommand);
- 更明确的表示当前正在使用命令模式或者除了执行命令之外,将来有可能提供撤销的命令等操作, 改动如下
var RefreshMenuBarCommand = function(receiver) {
return {
excute: function(){
receiver.refresh();
}
}
}
var setCommand = function(button,comand) {
button.onclick = function() {
comand.excute();
}
}
## 组合模式
- *宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令*
- 案例代码:智能家居命令,关门,开电脑,登录qq一系列命令
- ```js
var closeDoorCommand = {
excute: function() {
console.log('关门');
}
}
var openPcCommand = {
excute: function() {
console.log('开电脑');
}
}
var loginQQCommand = {
excute: function() {
console.log('登录qq');
}
}
var MacroCommand = function() {
return {
commandList: [],
add: function(command) {
this.commandList.push(command);
},
excute: function() {
for(var i = 0, command; command = this.commandList[i++];) {
command.excute();
}
}
}
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand);
macroCommand.add(loginQQCommand);
macroCommand.add(openPcCommand);
macroCommand.excute();
- marciCommand被称为组合对象,closeDoorCommand、loginQQCommand、openPcCommand都是叶对象
- 优点:
1、提供一种遍历树形结构的方案,通过调用组合对象的excute方法,程序会递归调用组合对象下的excute方法,组合模式可以非常方便地描述对象部分-整体层次结构。
2、利用对象多态性和统一对待组合对象和单个对象 - 组合模式的树传递:请求从树最顶端的对象往下传递,如果当前处理请求的对象是叶对象(普通子命令),叶对象自身会对请求作出相应的处理。如果当前处理请求的对象是组合对象(宏命令),组合对象则会遍历它属下的子节点,将请求继续传递给子节点。
更复杂的宏命令
- 关门,开电脑,登录qq为一个子命令,打开电视和音响为一个子命令,打开电脑为一个子节点
var MacroCommand = function() {
return {
commandList: [],
add: function(command) {
this.commandList.push(command);
},
excute: function() {
for(var i = 0, command; command = this.commandList[i++];) {
command.excute();
}
}
}
}
//开空调为子对象
var openAcCommand = {
excute: function() {
console.log('开空调');
}
}
//开电脑、登录qq、关门组合命令
var closeDoorCommand = {
excute: function() {
console.log('关门');
}
}
var openPcCommand = {
excute: function() {
console.log('开电脑');
}
}
var loginQQCommand = {
excute: function() {
console.log('登录qq');
}
}
var macroCommand1 = MacroCommand()
macroCommand1.add(closeDoorCommand);
macroCommand1.add(loginQQCommand);
macroCommand1.add(openPcCommand);
//打开电视、打开音响组合命令
var openTvCommand = {
excute: function() {
console.log('打开电视');
}
}
var openSoudCommand = {
excute: function() {
console.log('打开音响');
}
}
var macroCommand2 = MacroCommand()
macroCommand2.add(openTvCommand);
macroCommand2.add(openSoudCommand);
//总组合命令
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
macroCommand.excute();
- 组合模式实例 -- 扫描文件夹
- ```js
var Folder = function(name) {
this.name = name;
this.files = []
}
Folder.prototype.add = function(file) {
this.files.push(file);
}
Folder.prototype.scan = function() {
console.log('开始扫描文件夹' + this.name);
for(var i = 0, file; file = this.files[i++];) {
file.scan();
}
}
var File = function(name) {
this.name = name;
}
File.prototype.add = function(file) {
throw new Error('文件下面不能再加文件');
}
File.prototype.scan = function() {
console.log('开始扫描文件' + this.name);
}
var folder = new Folder('学习资料');
var folder1 = new Folder('javascript');
var folder2 = new Folder('nodejs');
var file1 = new File('js设计模式');
var file2 = new File('js高级设计程序');
var file3 = new File('nodejs深入浅出');
folder1.add(file1)
folder1.add(file2)
folder2.add(file3)
folder.add(folder1)
folder.add(folder2)
folder.scan();
- 扫描文件增强版 -- 引用父对象,加删除功能
- 当this.parent不为null时不做任何操作
## 模板方法模式 -- 继承
- 模板方法方式是一种只需用继承就可以实现的简单模式
- 由两部分结构:第一部分是抽象父类,第二部分是具体实现的实现子类
- 案例代码:咖啡和茶
- 咖啡需求:
1、把水煮沸
2、用沸水冲泡咖啡
3、把咖啡倒进杯子
4、加糖和牛奶
- ```js
var Coffee = function() {}
Coffee.prototype.boilWater = function() {
console.log('把水煮沸');
}
Coffee.prototype.brewCoffee = function() {
console.log('沸水冲泡咖啡');
}
Coffee.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
}
Coffee.prototype.addSugarAndMilk = function() {
console.log('加糖和牛奶');
}
Coffee.prototype.init = function() {
this.boilWater();
this.brewCoffee();
this.pourInCup();
this.addSugarAndMilk();
}
- 茶叶需求:
1、把水煮沸
2、用沸水浸泡咖啡
3、把茶水倒进杯子
4、加lemon
var Tea = function() {}
Tea.prototype.boilWater = function() {
console.log('把水煮沸');
}
Tea.prototype.steepTea = function() {
console.log('用沸水浸泡茶叶');
}
Tea.prototype.pourInCup = function() {
console.log('把茶水倒进杯子');
}
Tea.prototype.addLemon = function() {
console.log('加柠檬');
}
Tea.prototype.init = function() {
this.boilWater();
this.steepTea();
this.pourInCup();
this.addLemon();
}
- 抽离需求共同点
1、把水煮沸
2、用沸水冲泡饮料
3、把饮料倒进杯子
4、加调料
- ```js
var Common = function() {}
Common.prototype.boilWater = function() {
console.log('把水煮沸');
}
Common.prototype.brew = function() {
throw new Error('子类必须重写brew方法');
}
Common.prototype.pourInCup = function() {
throw new Error('子类必须重写pourInCup方法');
}
Common.prototype.addOthers = function() {
throw new Error('子类必须重写addOthers方法');
}
Common.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
this.addOthers();
}
//创建Coffee子类和Tea子类
var Coffee = function() {}
Coffee.prototype = new Common();
Coffee.prototype.brew = function() {
console.log('沸水冲泡咖啡');
}
Coffee.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
}
Coffee.prototype.addOthers = function() {
console.log('加糖和牛奶');
}
var coffee = new Coffee();
coffee.init();
var Tea = function() {}
Tea.prototype = new Common();
Tea.prototype.brew = function() {
console.log('沸水冲泡茶叶');
}
Tea.prototype.pourInCup = function() {
console.log('把茶水倒进杯子');
}
Tea.prototype.addOthers = function() {
console.log('加柠檬');
}
var tea = new Tea();
tea.init();
- 模板方法模式常被架构师用于搭建项目的框架,架构师定好了框架的骨架,程序员继承框架的结构之后负责往里面填空。
钩子方法
- 假如需求中某些需求是不需要加其他东西的,则钩子方法可以用来解决这个问题
Common.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.isNeedOthers()) {
this.addOthers();
};
}
- 在javascript中的继承
- ```js
var Common = function(params) {
var boilWater = function() {
console.log('把水煮沸');
}
var brew = params.brew || function() {
throw new Error('子类必须重写brew方法');
}
var pourInCup = params.pourInCup || function() {
throw new Error('子类必须重写pourInCup方法');
}
var addOthers = params.addOthers || pourInCup function() {
throw new Error('子类必须重写addOthers方法');
}
var isNeedOthers = params.isNeedOthers || function() {
return true;
}
var F = function() {}
F.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.isNeedOthers()) {
this.addOthers();
};
}
return F;
}
var Coffee = Common({
brew: function() {
console.log('把咖啡倒进杯子');
},
pourInCup: function() {
console.log('把咖啡倒进杯子');
},
addOthers: function() {
console.log('加糖和牛奶');
},
isNeedOthers: function() {
return false;
}
})
var coffee = new Coffee();
coffee.init();
享元模式
- 运用共享技术来支持大量细粒度的对象
- 适用场景:系统中因为大量类似的对象而导致内存占用过高,浏览器特别是移动端的浏览器分配的内存并不算多的时候
- 案例:服装厂有50中男士衣服和50种女士衣服,需要50个男模特和50个女模特分别穿上一件衣服拍照,不使用享元模式的情况下:
var Model = function(sex, clothes) {
this.sex = sex;
this.clothes = clothes;
}
Model.prototype.takePhoto = function() {
console.log('sex=' + this.sex + 'clothes=' + clothes)
}
for(var i = 0; i < 50; i++) {
var maleModel = new Model('male', 'clothes' + i);
maleModel.takePhoto();
}
for(var i = 0; i < 50; i++) {
var femaleModel = new Model('female', 'clothes' + i);
femaleModel.takePhoto();
}
- 问题:存在太多的对象占用内存
- 解决思路:男女模特只需一个来试穿男士衣服和女士衣服
- ```js
var Model = function(sex) {
this.sex = sex;
}
Model.prototype.takePhoto = function() {
console.log('sex=' + this.sex + 'clothes=' + clothes)
}
var maleModel = new Model('male');
var femaleModel = new Model('female');
for(var i = 0; i < 50; i++) {
maleModel.clothes = 'clothes' + i
maleModel.takePhoto();
}
for(var i = 0; i < 50; i++) {
femaleModel.clothes = 'clothes' + i
femaleModel.takePhoto();
}
享元模式的内部状态和外部状态
- 享元模式要求将对象的属性划分为内部状态和外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量。
- 内部状态存储于对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体的场景,通常不会改变(这里sex是内部属性即内部状态)
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能共享(这里的clothes是外部属性即外部状态)
享元模式的问题
1、代码中显式创建了两个共享对象,但是实际中并不一定用到两个共享对象;用对象工厂来解决这个问题,当共享对象真正被需要的时候才去创建
2、手动设置clothes属性在更复杂的系统中并不是一个好的方式,由于的系统的复杂导致与共享对象的联系变得困难;用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。
- 实际案例 - 文件上传
- 上传文件数量过多时对象爆炸, 假设有flash上传和插件上传两种
var Upload = function(uploadType, fileName, fileSize) {
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom = null;
}
Upload.prototype.init = function(id) {
var that = this;
this.id = id;
this.dom = document.create('div');
this.dom.innerHTML = '文件名称:'+this.fileName+'文件大小'+ this.fileSize +'';
this.dom.querySelector('.delFile').onclick = function() {
that.delFile();
}
document.appendChild(this.dom);
}
Upload.prototype.delFile = function() {
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
};
if (window.confirm('确定要删除该文件吗?'+ fileName)) {
return this.dom.parentNode.removeChild(this.dom);
};
}
var id = 0;
var startUpload = function(uploadType, files) {
for(var i = 0, file; file = files[i++];) {
var uploadObj = new upload(uploadType, file.fileName, file.fileSize);
uploadObj.init(id++);
}
}
//创建3个插件上传对象和3个flash上传对象
startUpload('plugin', [{
fileName: '1.txt',
fileSize: 1000
},{
fileName: '2.txt',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}]);
startUpload('flash', [{
fileName: '4.txt',
fileSize: 1000
},{
fileName: '5.txt',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}]);
- 文件上传享元模式重构
- 划分内部状态是uploadType,因为一旦明确了uploadType,无论我们使用什么方式上传,这个上传对象都是可以被任何文件共用的,而filename和filesize是根据场景而变化的,每个文件的filename和filesize都不一样,没有办法被共享,只能被划分到外部状态。
- 剥离外部状态
- ```js
var Upload = function(uploadType) {
this.uploadType = uploadType;
}
//不需要init方法,初始化的upload对象由管理器管理
//开始删除之前需要读取文件的实际大小,而文件的实际大小被储存在外部管理器uploadManager
Upload.prototype.delFile = function() {
uploadManager.setExtetnalState(id, this);
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
};
if (window.confirm('确定要删除该文件吗?'+ fileName)) {
return this.dom.parentNode.removeChild(this.dom);
};
}
- 工厂进行对象实例化,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象
var UploadFactory = (function() {
var createFlyWeightObjs = {};
return {
create: function(uploadType) {
if (createFlyWeightObjs[uploadType]) {
return createFlyWeightObjs[uploadType]
};
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})()
- 管理器封装外部状态,它负责向UploadFactory提交创建对象的请求,并用一个uploadDatabase对象保存所有的upload对象的外部状态
- ```js
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function(id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = '文件名称:'+this.fileName+'文件大小'+ this.fileSize +'';
dom.querySelector('.delFile').onclick = function() {
flyWeightObj.delFile();
}
document.appendChild(dom);
uploadDatabase[id] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
}
return flyWeightObj;
},
setExtetnalState: function(id, flyWeightObj) {
var uploadData = uploadDatabase[id];
for(var i in uploadData) {
flyWeightObj[i] = uploadData[i];
}
}
}
})();
var startUpload = function(uploadType, files) {
for(var i = 0, file; file = files[i++];) {
var uploadObj = uploadManager.add(id++, uploadType, file.fileName, file.fileSize);
}
}
//创建3个插件上传对象和3个flash上传对象
startUpload('plugin', [{
fileName: '1.txt',
fileSize: 1000
},{
fileName: '2.txt',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}]);
startUpload('flash', [{
fileName: '4.txt',
fileSize: 1000
},{
fileName: '5.txt',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}]);
- 享元模式的适用性
1、一个程序中使用了大量的相似对象
2、由于使用了大量的对象,造成很大的内存开销
3、对象的大多数状态都可以变为外部状态
4、剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象 - 当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂
对象池
- 对象池维持一个装载空闲对象的池子,如果需要对象的时候,不是直接new,而是转从对象池里获取。如果对象池里没有空闲对象则创建一个新对象,当获取出的对象完成它的职责之后,再进入池子等待被下次获取
- 对象池使用最多的场景是跟dom操作有关的。很多空间和时间都消耗在了DOM节点上
- 实际案例:mobike单车的定位,某一次的定位单车小图标可以与第二次的单车有重复的节点
var toolTipFactory = (function(){
var toolTipPool = [];
return {
create: function() {
if (toolTipPool.length === 0) {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
} else {
return toolTipPool.shift();
}
},
recover: function(toolTipDom) {
return toolTipPool.push(toolTipDom);
}
}
})();
//创建2个图标节点,用一个数组记录方便回收
var ary = [];
for(var i = 0, str; str = ['A', 'B'][i++];) {
var toolTip = toolTipFactory.create();
toolTipPool.innerHTML = str;
ary.push(toolTip);
}
//第二次搜索重新绘制之前将两个节点回收进对象池
for(var i = 0, toolTip; toolTip = ary[i++];) {
toolTipFactory.recover(toolTip);
}
//再创建6个小气泡
var ary = [];
for(var i = 0, str; str = ['A', 'B','C', 'D','E','F'][i++];) {
var toolTip = toolTipFactory.create();
toolTipPool.innerHTML = str;
ary.push(toolTip);
}
- 通用对象池实现
- ```js
var toolTipFactory = function(fn){
var toolTipPool = [];
return {
create: function() {
var obj = toolTipPool.length === 0 ? fn.apply(this, arguments) : toolTipPool.shift();
return obj;
},
recover: function(toolTipDom) {
return toolTipPool.push(toolTipDom);
}
}
};
var createDivFactory = toolTipFactory(function(str){
var div = document.createElement('div');
div.innerHTML = str;
document.body.appendChild(div);
return div;
});
var ary = [];
for(var i = 0, str; str = ['A', 'B'][i++];) {
var toolTip = createDivFactory.create(str);
ary.push(toolTip);
}
职责链模式
- 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
- 情景案例:公司对支付过定金的用户有一定的优惠政策。在正式购买之后,已经支付500元定金的用户会收到100元商城优惠券,200元定金的用户可以收到50元优惠券,而没有支付定金的普通用户没有优惠券。
- 后台的字段:
- orderType: 订单类型,1,500元的定金用户;2,200元的定金用户;3:普通用户
- pay:是否已经支付定金
- stock 表示当前用哪个与普通购买的手机库存量,已经支付过500或者200定金的用户不受此限制
- 后台的字段:
- 代码示例
var order = function(orderType, pay, stock) {
if (orderType === 1) {
if (pay === true) {
console.log('500元定金已付,得到100优惠券');
} else {
if (stock > 0) {
console.log('普通购买模式,无优惠券');
} else {
console.log('手机库存不足');
}
}
} else if( orderType === 2) {
if (pay === true) {
console.log('200元定金已付,得到50优惠券');
} else {
if (stock > 0) {
console.log('普通购买模式,无优惠券');
} else {
console.log('手机库存不足');
}
}
} else if( orderType === 3) {
if (stock > 0) {
console.log('普通购买模式,无优惠券');
} else {
console.log('手机库存不足');
}
}
}
orderType(1, true, 500)
- 重构代码:将500元订单、200元订单以及普通购买分成3个函数,先将3个字段传给500元订单函数,如果不符合处理条件,则将请求传递给后面的200元订单函数,如果200元订单函数依然不能处理该请求,则继续传递请求给普通函数
- ```js
var order500 = function(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500元定金已付,得到100优惠券');
} else {
order200(orderType, pay, stock);
}
}
var order200 = function(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200元定金已付,得到50优惠券');
} else {
orderNormal(orderType, pay, stock);
}
}
var orderNormal = function(orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买模式,无优惠券');
} else {
console.log('手机库存不足');
}
}
order500(1, true, 500); //500元定金已付,得到100优惠券
order500(1, false, 500);//普通购买模式,无优惠券
order500(2, true, 500); //200元定金已付,得到50优惠券
order500(2, false, 500); //普通购买模式,无优惠券
order500(3, false, 0); //手机库存不足
- 重构后的代码仍然存在传递代码被耦合在业务函数中
- 改写三个函数,约定如果不能处理请求则返回一个特定的字符串表示该请求需要往后面传递,这里约定为‘next’
var order500 = function(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500元定金已付,得到100优惠券');
} else {
return 'next';
}
}
var order200 = function(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200元定金已付,得到50优惠券');
} else {
return 'next';
}
}
var orderNormal = function(orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买模式,无优惠券');
} else {
console.log('手机库存不足');
}
}
- 定义一个构造函数Chain传入需要被包装的函数,拥有一个实例属性this.successor,表示在链中的下一个节点
- ```js
var Chain = function(fn) {
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNext = function(successor) {
return this.successor = successor;
}
Chain.prototype.passRequset = function() {
var result = this.fn.apply(this, arguments);
if (result === 'next') {
return this.successor && this.successor.passRequset.apply(this.successor, arguments);
};
return result;
}
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
chainOrder500.setNext(chainOrder200);
chainOrder200.setNext(chainOrderNormal);
chainOrder500.passRequset(1, true, 500); //500元定金已付,得到100优惠券
chainOrder500.passRequset(1, false, 500);//普通购买模式,无优惠券
chainOrder500.passRequset(2, true, 500); //200元定金已付,得到50优惠券
chainOrder500.passRequset(2, false, 500); //普通购买模式,无优惠券
chainOrder500.passRequset(3, false, 0); //手机库存不足
- 职责链的异步形式
- 增加一个主动触发的函数在异步调用时使用
var fn1 = new Chain(function() { console.log(1); return 'next'; }); var fn2 = new Chain(function() { console.log(2); var self = this; setTimeout(function() { self.next(); }, 1000); }); var fn3 = new Chain(function() { console.log(3); }); fn1.setNext( fn2).setNext(fn3); fn1.passRequset();
## 中介者模式
- 作用是解除对象与对象之间的紧耦合关系。增加一个中介者对象之后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
- 案例代码:泡泡堂游戏
- ```js
// version: 两个玩家版本
function Player(name) {
this.name = name;
this.enemy = null;
}
Player.prototype.win = function() {
console.log(this.name + 'win');
}
Player.prototype.lose = function() {
console.log(this.name + 'lose');
}
Player.prototype.die = function() {
this.lose();
this.enemy.win();
}
//创建两个玩家
var player1 = new Player('one');
var player2 = new Player('two');
//互相设置敌人
player1.enemy = player2;
player2.enemy = player1;
player1.die(); //输出:one lose, two win
- 为游戏增加队伍
var players = [];
function Player(name, teamColor) {
this.partners = [];
this.enemies = [];
this.state = 'live';
this.name = name;
this.teamColor = teamColor;
}
Player.prototype.win = function() {
console.log(this.name + 'win');
}
Player.prototype.lose = function() {
console.log(this.name + 'lose');
}
// 每个玩家死亡的时候都遍历其他队友的生存状况
Player.prototype.die = function() {
var all_dead = true;
this.state = 'dead';
for (var i = 0, partner; partner = this.partners[i++];) {
if (partner.state !== 'dead') {
all_dead = false;
break;
};
}
if (all_dead === true) {
this.lose();
for (var i = 0, partner; partner = this.partners[i++];) {
partner.lose();
}
for (var i = 0, enemy; enemy = this.enemies[i++];) {
enemy.win();
}
};
}
var playerFactory = function(name, teamColor) {
var newPlayer = new Player(name, teamColor);
for(var i = 0, player; player = players[i++];) {
if (player.teamColor === newPlayer.teamColor) {
player.partners.push(newPlayer);
newPlayer.partners.push(player);
} else {
player.enemies.push(newPlayer);
newPlayer.enemies.push(player);
}
}
players.push(newPlayer);
return newPlayer;
}
var player1 = playerFactory(1, 'red');
var player2 = playerFactory(2, 'red');
var player3 = playerFactory(3, 'red');
var player4 = playerFactory(4, 'red');
var player5 = playerFactory(5, 'blue');
var player6 = playerFactory(6, 'blue');
var player7 = playerFactory(7, 'blue');
var player8 = playerFactory(8, 'blue');
player1.die()
player2.die()
player3.die()
player4.die()
- 玩家增多带来的麻烦
- 当每个对象状态发生改变都必须要显式地遍历通知其他对象
- 用中介者模式改造游戏
- ```js
function Player(name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = 'alive';
}
Player.prototype.win = function() {
console.log(this.name + 'win');
}
Player.prototype.lose = function() {
console.log(this.name + 'lose');
}
//改为向中介者发送死亡消息
Player.prototype.die = function() {
this.state = 'dead';
playerDirector.ReceiveMessage('playerDead', this);
}
Player.prototype.remove = function() {
playerDirector.ReceiveMessage('removePlayer', this);
}
Player.prototype.changeTeam = function(color) {
playerDirector.ReceiveMessage('changeTeam', this, color);
}
//重写工厂函数
var playerFactory = function(name, teamColor) {
var newPlayer = new Player(name, teamColor);
playerDirector.ReceiveMessage('addPlayer', newPlayer);
return newPlayer;
}
var playerDirector = (function(){
var players = {},
operations = {};
operations.addPlayer = function(player) {
var teamColor = player.teamColor;
players[teamColor] = players[teamColor] || [];
players[teamColor].push(player);
}
operations.removePlayer = function(player) {
var teamColor = player.teamColor,
teamPlayers = players[teamColor] || [];
for (var i = teamPlayers.length - 1; i >= 0; i--) {
if ( teamPlayers[i] === player) {
teamPlayers.splice(i, 1);
}
};
}
operations.changeTeam = function (player, newTeamColor) {
operations.removePlayer(player);
player.teamColor = newTeamColor;
operations.addPlayer(player);
}
operations.playerDead = function(player) {
var teamColor = player.teamColor,
teamPlayers = players[teamColor];
var all_dead = true;
for(var i = 0, player; player = teamPlayers[i++];) {
if (player.state !== 'dead') {
all_dead = false;
break;
};
}
if (all_dead === true) {
for(var i = 0, player; player = teamPlayers[i++];) {
player.lose();
}
for(var color in players) {
if (color !== teamColor) {
var otherTeam = players[color];
for(var i =0, player; player = otherTeam[i++];) {
player.win();
}
};
}
};
}
var ReceiveMessage = function() {
var message = Array.prototype.shift.call(arguments);
operations[message].apply(this, arguments);
}
return {
ReceiveMessage: ReceiveMessage
}
})()
var player1 = playerFactory(1, 'red');
var player2 = playerFactory(2, 'red');
var player3 = playerFactory(3, 'red');
var player4 = playerFactory(4, 'red');
var player5 = playerFactory(5, 'blue');
var player6 = playerFactory(6, 'blue');
var player7 = playerFactory(7, 'blue');
var player8 = playerFactory(8, 'blue');
// player1.die()
// player2.die()
// player3.die()
// player4.die()
// player1.remove()
// player2.remove()
// player3.die()
// player4.die()
player1.changeTeam('blue')
player2.die()
player3.die()
player4.die()
- 实例:购买商品
- 在购买手机的流程中可以选择手机的颜色以及输入购买数量,同时页面中有两个展示区域,分别向用户展示刚刚选择好的数量和颜色
输入购买数量:
你选了颜色是:
你选了数量是:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2986255-2703eb84de135da2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 可能面临的问题
- 如果要改变展示区域就要深入到事件内部代码修改
- 当增加判断条件是要每个事件都去判断,如:增加手机内存的选择,每个事件都要增加判断条件,同时要新增手机内存的选择事件
- 增加内存判断后的代码:
- ```html
输入购买数量:
你选了颜色是:
你选了内存是:
你选了数量是:
- 每个对象都耦合在一起,改变或者增加任何一个节点对象都要通知到与其相关的对象
var goods = {
'red|32G': 3,
'red|64G': 0,
'blue|32G': 1,
'blue|64G': 6
}
var mediator = (function(){
var colorSelect = document.getElementById('colorSelect');
var memorySelect = document.getElementById('memorySelect');
var numberInput = document.getElementById('numberInput');
var colorInfo = document.getElementById('colorInfo');
var numberInfo = document.getElementById('numberInfo');
var memoryInfo = document.getElementById('memoryInfo');
return {
changed: function(obj) {
var color = this.value,
memory = memorySelect.value,
number = numberInput.value,
stock = goods[color + '|' + memory];
if (obj === colorSelect) {
colorInfo.innerHTML = color;
} else if(obj === memorySelect) {
memoryInfo.innerHTML = memory;
} else if(obj === numberInput) {
numberInput.innerHTML = number;
}
if (!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
};
if (!memory) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择内存大小';
};
if (( (number - 0 ) | 0) !== number - 0) {
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
};
if (number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = '库存不足';
return;
};
nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
}
})()
colorSelect.onchange = function() {
mediator.changed(this);
}
memorySelect.onchange = function() {
mediator.changed(this);
}
numberInput.oninput = function() {
mediator.changed(this);
}