定义:为对象动态添加新行为。
圣诞节快到了,很多小伙伴都会装饰圣诞树,我们会往树上挂上很多有节日气氛的装饰品,但我们并不会破坏这棵树原有的结构,这便是我们生活中的装饰器模式。
装饰函数
JavaScrip
t中装饰器模式的一个很好地表现便是装饰函数
。比如我们在维护一个项目的时候,突然来了新的需求,需要我们往原来的函数中添加新的功能。原来的函数是以前的同事写的又经过好几个人的手,里面的实现非常杂乱,最好的方法是尽量不去改动原函数,通过保存原引用的方式去改写这个函数。
比如我们想给window
绑定onload
事件,但是如果这个事件之前有人绑定过,我们直接写就会覆盖掉之前事件中的行为。为了避免覆盖掉之前的window.onload
函数中的行为,我们一般都会先保存好原先的window.onload
,把它放入新的 window.onload
里执行
window.onload = function () {
console('原先执行的行为');
};
const _onload = window.onload || function () {};
window.onload = function () {
_onload();
console.log('添加新行为');
};
新的window.onload
函数就是我们的装饰函数。
给函数动态添加新行为
上面这种方式存在以下两个问题:
- 必须维护_onload这个中间变量,如果函数装饰链较长,就需要更多的中间变量。
- this指向的问题,函数作为对象的方法被调用时,this指向该对象(上面的例子不存在该问题,但我们应该考虑到原来的函数实现有用到this的情况)。
为了解决这两个问题,我们用高阶函数
来装饰函数。
我们在原来的函数上添加新行为,添加的新行为要么先于函数执行,要么后于函数执行。我们实现两个方法——Function.prototype.before
给函数添加先于函数执行的新行为,Function.prototype.after
给函数添加后于函数执行的新行为:
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () {
// 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
// 并且保证 this 不被劫持
};
};
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
};
};
再回到上面window.onload
的例子,我们可以这样写:
window.onload = function () {
console('原先执行的行为');
};
window.onload = (window.onload || function () {}).after(function () {
console.log('添加新行为');
}).after(function () {
console.log('继续添加其他新行为');
});
事件埋点
假如我们给搜索按钮添加埋点事件,需要做两件事,一是实现搜索功能,另一个是上报数据。
// 普通实现
const btnCLick = () => {
console.log("搜索功能");
console.log("上报数据");
};
// 装饰器模式实现
const search = () => {
console.log("搜索功能");
};
const sendData = () => {
console.log("上报数据");
};
const btnCLick = search.after(sendData);
在第二种实现方式(装饰器模式实现)中,我们对按钮职责进行了更细的划分,保证了函数单一职责原则。
axios请求加上token参数
下面是一个我们封装的axios
请求
var request = function(url, type, data){
console.log(data);
// 具体代码略
};
现在需要给每个请求都加上token
参数
// 普通实现
var request = function(url, type, data = {}){
data.token = getToken();
console.log(data);
};
// 装饰器模式实现
var request = function(url, type, data = {}){
console.log(data);
};
request = request.before((url, type, data = {})=>{
data.token = getToken();
})
在装饰器模式实现方式中我们没有改变原函数,原函数是一个比较纯净的单一职责函数,提高了request函数的可复用性。