场景


在了解这个设计模式之前,我们首先要知道什么场景下会需要用到这个。在开发需求时,我们经常会遇到需要动态改变属性或者方法内容。


举个例子,改变地址拼接,javascript是个非常方便的编程语言可以很方便的实现。


let customer = {
	name :'张三',
  address:'杭州市'
} 

customer.address = '浙江省' + customer.address ;



类实现装饰


我们需要一个更好的案例,升级customer为一个类,比如一个寄送方式,基于不同的用户等级,我们会有不同的方式寄送。于是我们看到了最终的结果中,我们的用户最终有三种方式寄送,而且没有影响原来的方法实现。


let Customer = function(){
} 

Customer.prototype.send = function(){
 console.log('支持普通寄送');
}

const flySendDecorator = function(customer){
	this.customer = customer;
}
flySendDecorator.prototype.send = function(){
	this.customer.send();
  console.log('还支持航空寄送');
}
const seaSendDecorator = function(customer){
	this.customer = customer;
}
seaSendDecorator.prototype.send = function(){
	this.customer.send();
  console.log('还支持航海寄送');
}

let xiaozhang = new Customer();
xiaozhang = new flySendDecorator(xiaozhang);
xiaozhang = new seaSendDecorator(xiaozhang);
xiaozhang.send();



方法直接实现装饰


js的方法非常灵活,其实我们可以直接在方法体内直接实现这种效果,方式也很简单,直接新写方法执行已有方法的同时执行新方法。


let customer = {
	send:function(){
    console.log('支持普通寄送')
  }
}
const flySendDecorator = function(){
  console.log('还支持航空寄送');
}
const seaSendDecorator = function(){
  console.log('还支持航海寄送');
}

let send1 = customer.send;
customer.send = function(){
	send1();
  flySendDecorator();
}

let send2 = customer.send;
customer.send = function(){
	send2();
  seaSendDecorator();
}
customer.send()


装饰函数


其实装饰函数就是上面的函数的实现过程的进一步抽象,在很多时候,我们希望保留原有函数的情况下扩展新的功能,都是先复制原来的函数,然后加入自己的功能。


let a = function(){
}
let _a = a
a = function(){
 _a();
 // codes here  
}



这种最常见的是onload,我们经常需要在onload时机的情况下执行很多函数,但不确定是否别人已经绑定过其他事件,那么这种情况下的写法就是下面的方式。


window.onload = function(){

}

let _onload = window.onload || function(){} ;
window.onload = function(){
	_onload();
  // your codes here
}


Aop 函数


在下面的函数原型中,我们追加了前置和后置的函数钩子,可以直接实现改变函数原型,可以很方便的实现函数运行前和后执行指定的函数


Function.prototype.before = function(beforefn){
	let _self = this;
  return function(){
    beforefn.apply(this,arguments)
  	let ret = _self.apply(this,arguments);
    return ret
  }
}

Function.prototype.after = function(afterfn){
	let _self = this;
  return function(){
    afterfn.apply(this,arguments)
  	let ret = _self.apply(this,arguments);
    return ret
  }
}

let a = function(){
  console.log(1)
}
a = a.before(function(){
  console.log(0)
})

a() // 分别打印出0 1 非常方便


应用


数据上报


我们的api请求数据前后可以进行数据的上报,可以基于这种设计模式进行改造。


改变参数


在我们执行函数之前,很多时候,我们需要将参数进行改造,那么如果这个参数改造是可以提炼为公用代码的,那么直接用设计模式实现是非常赞的一个想法。



插件式的表单验证


我们经常会写表单的提交,也经常会提交的时候做一些参数的验证。


function submit(){
	if(username === ''){
  	return 
  }
  if(password === ''){
  	return 
  }
  let params = {
  	username,
    password
  }
  ajax.sendRequest(params)

} 


有些经验的同学会处于函数单一职责的原则,将验证的函数进行独立拆分,nice!


function validateData(){
	if(username === ''){
  	return false;
  }
  if(password === ''){
  	return false;
  }
  return true;
}
function submit(){
	if(validateData()){
  	let params = {
  	username,
    password
  	}
  ajax.sendRequest(params);
  }
  return 
} 


既然我们学了设计模式,有什么是可以做的呢?当然有,我们发现我们是在验证通过后才执行代码后面的逻辑,那么其实就可以根据函数的before进行定义。


Function.prototype.before = function(beforefn){
	let _self = this;
  return function(){
    // 发现返回false的时候 直接不执行
    if(!beforefn.apply(this,arguments)){
    	return
    }
  	let ret = _self.apply(this,arguments);
    return ret
  }
}

function submit(){
  	let params = {
  	username,
    password
  	}
  ajax.sendRequest(params);
} 
submit.before(validateData)


es7中的装饰器

类修饰


在下面的类修饰中,我们只是基于入参,进行期望的参数改造,就可以达到我们想要的目的而不改变函数整体。这样在需要的情况下,我们只要对相应的类追加需要的装饰器即可。


function decorateArmour(target, key, descriptor) {
  const method = descriptor.value;
  let moreDef = 100;
  let ret;
  descriptor.value = (...args)=>{
    args[0] += moreDef;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3


修饰的原理


其实现方式是调用Object.defineProperty(obj, prop, descriptor) ,这 3 个参数分别代表:

  1. obj: 目标对象
  2. prop: 属性名
  3. descriptor: 针对该属性的描述符


注意事项

只有类和类方法可以用修饰器,函数直接使用会有变量提升的问题。



参考文档