JavaScript设计模式———装饰者模式

装饰者模式(使用装饰函数装饰对象)可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象

在 JavaScript 中,几乎一切都是对象,其中函数又被称为一等对象。在平时的开发工作中,也许大部分时间都在和函数打交道。在 JavaScript 中可以很方便地给某个对象扩展属性和方法,但却很难在不改动某个函数源代码的情况下,给该函数添加一些额外的功能。在代码的运行期间,我们很难切入某个函数的执行环境。

要想为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,但这是最差的办法,直接违反了开放-封闭原则

例子:
天气冷了, 就添加衣服来保暖;天气热了, 就将外套脱下;这个例子很形象地含盖了装饰器的神韵, 随着天气的冷暖变化, 衣服可以动态的穿上脱下。

let wear = function() {
     
  console.log('穿上第一件衣服')
}

let wear = function() {
     
  console.log('穿上第一件衣服')
  console.log('穿上第二件衣服')
}

通过保存原引用的方式就可以改写某个函数:

let wear = function() {
     
  console.log('穿上第一件衣服')
}
const _wear1 = wear
wear = function() {
     
  _wear1()
  console.log('穿上第二件衣服')
}
const _wear2 = wear
wear = function() {
     
  _wear2()
  console.log('穿上第三件衣服')
}
wear()
// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

这种方式有以下缺点:

  1. 临时变量会变得越来越多;
  2. this 指向有时会出错(函数中得this是可变得,再全局执行时this指向windows)

使用用 AOP 装饰函数

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;
    }
};

用后置代码来实验下上面穿衣服的例子:

const wear1 = function() {
     
  console.log('穿上第一件衣服')
}

const wear2 = function() {
     
  console.log('穿上第二件衣服')
}

const wear3 = function() {
     
  console.log('穿上第三件衣服')
}

const wear = wear1.after(wear2).after(wear3)
wear()

// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

但这样子有时会污染原生函数, 可以装饰函数抽离出来

const after = function(fn, afterFn) {
     
  return function() {
     
    fn.apply(this, arguments)
    afterFn.apply(this, arguments)
  }
}

const wear = after(after(wear1, wear2), wear3)
wear()

AOP 的应用实例

用AOP动态改变函数的参数

Function.prototype.before = function(beforefn) {
     
    var __self = this;
    return function() {
     
        beforefn.apply(this, arguments); // (1)
        return __self.apply(this, arguments); // (2)
    }
}

从 这 段 代 码 的 (1)处 和 (2)处 可 以 看 到 , beforefn 和 原 函 数 __self 共 用 一 组 参 数 列 表arguments,当我们在 beforefn 的函数体内改变 arguments 的时候,原函数__self 接收的参数列表自然也会变化。

var func = function(param) {
     
    console.log(param); // 输出: {a: "a", b: "b"}
}
func = func.before(function(param) {
     
    param.b = 'b';
});
func({
      a: 'a' });

例子:
现在有一个用于发起 ajax 请求的函数,这个函数负责项目中所有的 ajax 异步请求:

var ajax = function( type, url, param ){
     
console.dir(param);
// 发送 ajax 请求的代码略
};
ajax( 'get', 'http:// xxx.com/userinfo', {
      name: 'sven' } );

如果需要再参数中添加Token 参数(防止 CSRF 攻击),且希望ajax为一个干净的函数,不修改原函数:

var getToken = function() {
     
    return 'Token';
}
ajax = ajax.before(function(type, url, param) {
     
    param.Token = getToken();
});
ajax('get', 'http:// xxx.com/userinfo', {
      name: 'sven' });
//  ajax 请求的参数 {name: "sven", Token: "Token"}

装饰者模式和代理模式的比较:
装饰者模式代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求

代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。

代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代
理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。

装饰者模式的作用就是为对象动态加入行为。

换句话说,代理模式强调一种关系( Proxy 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定
装饰者模式用于一开始不能确定对象的全部功能时。

代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链。

总结

这种模式在实际开发中非常有用,除了上面提到的例子,它在框架开发中也十分有用。作为框架作者,我们希望框架里的函数提供的是一些稳定而方便移植的功能,那些个性化的功能可以在框架之外动态装饰上去,这可以避免为了让框架拥有更多的功能,而去使用一些 if、 else 语句预测用户的实际需要

你可能感兴趣的:(设计模式)