装饰器(decorator)
一个特殊的函数,它接受另一个函数并改变它的行为,它返回缓存包装器。
let worker = {
slow(min, max) {
alert(`Called with ${min},${max}`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); // 利用哈希函数缓存多参数
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments);
// 适用于对象方法,使用 “func.call” 设定上下文,普通函数不用。
//当我们期望可迭代对象时,使用 call,当我们期望类数组对象/真正的数组时,使用 apply!
cache.set(key, result);
return result;
};
}
// 哈希函数
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
- 呼叫转移(call forwarding): 将所有参数连同上下文一起传递给另一个函数。
通常是使用 apply 完成的:
let wrapper = function() {
return original.apply(this, arguments);
};
- 方法借用(method borrowing):使哈希函数适用于任何数量的参数。 从一个对象中获取一个方法,并在另一个对象的上下文中“调用”它。采用数组方法并将它们应用于参数 arguments 是很常见的。另一种方法是使用 Rest 参数对象,该对象是一个真正的数组。
function hash() {
alert( [].join.call(arguments) ); // 1,2
}
hash(1, 2);
//我们从常规数组 [].join 中获取(借用)join 方法,
并使用 [].join.call 在 arguments 的上下文中运行它。
原生方法 arr.join(glue) 的内部算法:
(1)让 glue 成为第一个参数,如果没有参数,则使用逗号 “,”。
(2)让 result 为空字符串。
(3)将 this[0] 附加到 result。
(4)附加 glue 和 this[1]。
(5)附加 glue 和 this[2]。
(6)……以此类推,直到 this.length 项目被粘在一起。
(7)返回 result。
因此,从技术上讲,它需要 this 并将 this[0],this[1] ……等 join 在一起。它的编写方式是故意允许任何类数组的 this 的(不是巧合,很多方法都遵循这种做法)。这就是为什么它也可以和 this=arguments 一起使用。
- 装饰器和函数属性:装饰后的函数将不再提供原始函数的属性。
一些包装器可能会提供自己的属性,eg.装饰器会计算一个函数被调用了多少次以及花费了多少时间,并通过包装器属性公开(expose)这些信息。
存在一种创建装饰器的方法,该装饰器可保留对函数属性的访问权限,但这需要使用特殊的 Proxy 对象来包装函数。我们将在后面的 Proxy 和 Reflect 中学习它。