设计模式之-装饰器模式

定义:为对象动态添加新行为。

圣诞节快到了,很多小伙伴都会装饰圣诞树,我们会往树上挂上很多有节日气氛的装饰品,但我们并不会破坏这棵树原有的结构,这便是我们生活中的装饰器模式。

装饰函数

JavaScript中装饰器模式的一个很好地表现便是装饰函数。比如我们在维护一个项目的时候,突然来了新的需求,需要我们往原来的函数中添加新的功能。原来的函数是以前的同事写的又经过好几个人的手,里面的实现非常杂乱,最好的方法是尽量不去改动原函数,通过保存原引用的方式去改写这个函数。

比如我们想给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函数就是我们的装饰函数。

给函数动态添加新行为

上面这种方式存在以下两个问题:

  1. 必须维护_onload这个中间变量,如果函数装饰链较长,就需要更多的中间变量。
  2. 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函数的可复用性。

你可能感兴趣的:(设计模式之-装饰器模式)