前言
上期我们学了工厂模式,单例模式,观察者模式,发布订阅模式,策略模式。
让我们继续学习吧。
代理模式 Proxy Pattern
代理模式是为一个对象提供一个代用品,以便控制对它的访问。
人话:购买者与卖房者中间的那个中介。
前端实现: 当调用一个方法Fn时,中间加入代理方法Proxy。先调用Proxy方法,Proxy内部再决定如何调用Fn方法。
举个例子:在房产销售中,有买房,卖方。如果买房一只去Call卖方。那卖方就将被烦死,所以需要中介的介入。
var Buyer = function (name){
this.name = name
}
Buyer.prototype.pay = function (money){
zhongjie.ask(money)
}
var zhongjie = {
ask(money){
if(money<100){
console.log("Sorry,钱不够不卖")
}else{
Seller.sell()
}
}
}
var Seller = {
sell:function(){
console.log("Ok,成交了");
}
}
var buyer1 = new Buyer("买家一");
buyer1.pay(50);
//log: Sorry,钱不够不卖
var buyer2 = new Buyer("买家二");
buyer1.pay(100);
//log: Ok,成交了
说明
像这种只有达成一定条件再去call真实方法的代理就叫做保护代理。
那这类代理在真实项目中可以做什么呢?
举个例子。
网站js错误收集
需求:当网页有任何Javascript错误时候就上传到服务器日志。
var uploadError = function (errorOrArray){
//ajax上传
//ajax(errorOrArray)
}
window.onerror = function (e){
uploadError(e);
}
是不是完成了?
嗯。
那么问题来了。
每一个error都要上传吗?我们凑满十个error再上传吧。减少请求,加入代理吧。
var uploadError = function (errorOrArray){
//ajax上传
//ajax(errorOrArray)
}
var uploadQueue = [];
var proxyUploadError = function (error){
uploadQueue.push(error);
if(uploadQueue.length >=10){
uploadError(uploadQueue);
uploadQueue = [];
}
}
window.onerror = function (e){
proxyUploadError(e);
}
是不是完成了?
嗯。
那么问题来了。
如果同时有大量error产生。比如某项目的scroll事件产生了几十万个error(true story)
凑满十个还是不够看呀。
我们换个策略吧。加入节流,并且忽略相同的报错。
var uploadError = function (errorOrArray){
//ajax上传
//ajax(errorOrArray)
console.log("上传成功")
}
var proxyUploadError = (function (error){
var errorQuene = [],
_uploadError = _.throttle(function (){
uploadError(errorQuene);
errorQuene = [];
}, 10000);//最多十秒上传一次
return function (error){
if(error重复){
return;
};
errorQuene.push(error);
_uploadError();
}
})
window.onerror = function (e){
proxyUploadError(e);
}
思考
Ok,一个简单的错误收集上传功能就完成了
那同学可能会问,为什么不把逻辑直接写入uploadError而要proxyUploadError呢?
1.职能单一,这样才可以很好的复用
2.在有些场景下。可以有多个Proxy存在,至于用哪个还是具体看情况。
其实这个收集器还缺个逻辑,想的出来吗?
职责链模式 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('DEV');
let a2 = new Action('TEST');
let a3 = new Action('UAT');
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();
//DEV 测试通过
//TEST 测试通过
//UAT 测试通过
说明
类似职责链模式的实现还有dom/组件中的父子节点事件不断的回调。
装饰器模式 Decorator Pattern
装饰器模式允许向一个现有的对象动态添加新的功能,同时又不改变其结构。继承来给对象增加功能来说,装饰器模式相比生成子类更为灵活优雅。
白话:想要什么直接拿来用就好了。
先让我们看看是如何实现的。
function Car() {
console.log("我是一辆普通车");
}
Car.prototype = {
drive: function () {
console.log('我能跑60码');
},
}
//创建装饰器
var Decorator = function (car) {
this.car = car;
}
// 装饰者要实现这些相同的方法
Decorator.prototype = {
drive: function () {
this.car.toString();
},
}
var Porsche = function (car){
Decorator.call(this, car);
console.log("升级成保时捷");
}
Porsche.prototype = new Decorator();
Porsche.prototype.drive = function (){
console.log('我能跑200码');
}
var Turbo = function (car){
Decorator.call(this, car);
console.log("装上涡轮增鸭");
}
Turbo.prototype = new Decorator();
Turbo.prototype.drive = function (){
console.log('我能跑500码');
}
var car = new Car();//我是一辆普通车
car.drive();//我能跑60码
var porsche = new Porsche(car);//升级成保时捷
porsche.drive();//我能跑200码
var turboCar = new Turbo(porsche);//装上涡轮增鸭
turboCar.drive();//我能跑500码
在es7之后只需
@Porsche
@Turbo
class Car{
drive(){}
}
说明
当然现在不必大费周折再这么写,但装饰器思想还是一样的。
外观模式 Facade Pattern
外观模式为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层借口,这个接口使得这一子系统更加容易使用。
白话:一个方法实现多个功能/适配单一功能。
前端实现:在一个方法里面利用多个判断进行操作。
function Sizzle( selector, context, results, seed ){
var m, i, elem, nid, match, groups, newSelector,
newContext = context && context.ownerDocument,
//上下文默认为document,节点类型默认为9
nodeType = context ? context.nodeType : 9;
results = results || [];
// 对选择器值为非字符串、为假、节点类型不符合要求时的返回值进行处理
if ( typeof selector !== "string" || !selector ||
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return results;
}
// 操作HTML文档
if ( !seed ) {
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
setDocument( context );
}
context = context || document;
if ( documentIsHTML ) {
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
// ID 选择器
if ( (m = match[1]) ) {
// 文档内容
if ( nodeType === 9 ) {
if ( (elem = context.getElementById( m )) ) {
if ( elem.id === m ) {
results.push( elem );
return results;
}
} else {
return results;
}
// 元素内容
} else {
if ( newContext && (elem = newContext.getElementById( m )) &&
contains( context, elem ) &&
elem.id === m ) {
results.push( elem );
return results;
}
}
// 类型选择器
} else if ( match[2] ) {
push.apply( results, context.getElementsByTagName( selector ) );
return results;
// Class 选择器
} else if ( (m = match[3]) && support.getElementsByClassName &&
context.getElementsByClassName ) {
push.apply( results, context.getElementsByClassName( m ) );
return results;
}
}
...
}
}
// 返回 调用select()方法后的值
return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
说明
可以看下jQuery的 Sizzle选择器
通过一层层的判断将3个功能合为一。
getElementById
getElementsByTagName
getElementsByClassName
当然这种做法早已过时,但jQuery在当年还是很辉煌的。
总结
设计模式总共有23种,适合前端的基本上就这几种。
灵活运用起来,让我们的代码看起来更优雅。