# 装饰器模式

JavaScript 在处理函数时提供了非凡的灵活性。它们可以被传递,用作对象,现在我们将看到如何在它们之间 转发(forward) 调用并 装饰(decorate) 它们。

假设我们有一个 CPU 重负载的函数 slow(x),但它的结果是稳定的。换句话说,对于相同的 x,它总是返回相同的结果。

如果经常调用该函数,我们可能希望将结果缓存(记住)下来,以避免在重新计算上花费额外的时间。

function slow(x) {
  // 这里可能会有重负载的 CPU 密集型工作
  alert(`Called with ${x}`);
  return x;
}

function cachingDecorator(func) {
  let cache = new Map();

  return function (x) {
    if (cache.has(x)) {
      // 如果缓存中有对应的结果
      return cache.get(x); // 从缓存中读取结果
    }

    let result = func(x); // 否则就调用 func

    cache.set(x, result); // 然后将结果缓存(记住)下来
    return result;
  };
}

在上面的代码中,cachingDecorator 是一个 装饰器(decorator):一个特殊的函数,它接受另一个函数并改变它的行为。

其思想是,我们可以为任何函数调用 cachingDecorator,它将返回缓存包装器。这很棒啊,因为我们有很多函数可以使用这样的特性,而我们需要做的就是将 cachingDecorator 应用于它们。

从外部代码来看,包装的 slow 函数执行的仍然是与之前相同的操作。它只是在其行为上添加了缓存功能。

上面提到的缓存装饰器不适用于对象方法。我们的包装器将调用传递给原始方法,但没有上下文 this。

有一个特殊的内置函数方法 func.call(context, …args),它允许调用一个显式设置 this 的函数。

let worker = {
  slow(x) {
    return x;
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function (x) {
    if (cache.has(x)) {
      console.log("is_cached");
      return cache.get(x);
    }
    let result = func.call(this, x); // 现在 "this" 被正确地传递了
    cache.set(x, result);
    return result;
  };
}
worker.slow = cachingDecorator(worker.slow); // 现在对其进行缓存
alert(worker.slow(2)); // 工作正常
alert(worker.slow(2)); // 工作正常,没有调用原始函数(使用的缓存)
// is_cached

当有多个参数的时候,我们可以使用func.apply

let result = func.apply(this, args);

call 和 apply 之间唯一的语法区别是,call 期望一个参数列表,而 apply 期望一个包含这些参数的类数组对象。 因此,这两个调用几乎是等效的:

func.call(context, ...args); // 使用 spread 语法将数组作为列表传递
func.apply(context, args); // 与使用 call 相同

这里只有很小的区别:

  • Spread 语法 ... 允许将 可迭代对象 args 作为列表传递给 call。
  • apply 仅接受 类数组对象 args