这是Decorator模式的一个工作示例:
class Dummy {
run() {
console.log('run');
}
}
function get() {
let instance = new Dummy();
instance.run = ((func) => {
return function() {
func();
console.log('decorator run');
}
})(instance.run);
return instance;
}
let obj = get();
obj.run();
但是,如果我们将 get 函数更改为:
function get() {
let instance = new Dummy();
instance.run = function() {
instance.run();
console.log('decorator run');
}
return instance;
}
我们将面临一个错误: VM68418:6 Uncaught RangeError:超出最大调用堆栈大小 在Dummy.instance.run(:6:32)
为什么会这样? instance.run 仍然是原始方法的包装器,没有“无用的”附加自执行功能。
我很高兴听到答案
答案 0 :(得分:3)
instance.run()
在其自己的定义中被调用,因此它导致永不结束的递归,从而导致最大调用堆栈大小超过错误。
答案 1 :(得分:3)
我认为纠结于诸如"装饰器","装饰器模式"或甚至"模式"等制造过的东西是危险的。在您的问题的核心,您有一个您希望改变其行为的函数,或decorate
...
const original = x =>
x * x
const decorate = f =>
x => f (x) + 1
const decorated =
decorate (original)
console.log (original (4)) // 16 4 * 4
console.log (decorated (4)) // 17 (4 * 4) + 1

因此,对于decorate
,我们正在捕捉这种递增的+ 1
效果,但请注意我们被迫决定何时递增; 之前或之后调用原始函数。也许在不同的变化中,我们希望"装饰"使用此+1效果,但在相反的时间。
下面,firstAdd1
是一个"装饰者"在调用原始函数之前递增。 thenAdd1
是一个装饰器,在调用原始函数后递增。
const original = x =>
x * x
const thenAdd1 = f =>
x => f (x) + 1
const firstAdd1 = f =>
x => f (x + 1)
const decoratedA =
thenAdd1 (original)
const decoratedB =
firstAdd1 (original)
console.log (original (4)) // 16 4 * 4
console.log (decoratedA (4)) // 17 (4 * 4) + 1
console.log (decoratedB (4)) // 25 (4 + 1) * (4 + 1)

但是现在我们已经重复了+1效果。 "装饰"事实证明,只是function composition。意识到这一点,我们消除了我们的计划中的痛苦和痛苦。
下面,我们在纯函数add1
中捕获+1效果,然后在给定f
之前或之后撰写
const add1 = x =>
x + 1
const compose = (f, g) =>
x => f (g (x))
const thenAdd1 = f =>
compose (add1, f)
const firstAdd1 = f =>
compose (f, add1)
制作此计划时没有任何对象受到伤害
const original = x =>
x * x
const add1 = x =>
x + 1
const compose = (f, g) =>
x => f (g (x))
const thenAdd1 = f =>
compose (add1, f)
const firstAdd1 = f =>
compose (f, add1)
const decoratedA =
thenAdd1 (original)
const decoratedB =
firstAdd1 (original)
console.log (original (4)) // 16 4 * 4
console.log (decoratedA (4)) // 17 (4 * 4) + 1
console.log (decoratedB (4)) // 25 (4 + 1) * (4 + 1)

当然,功能组合非常强大。我们可以修改compose
以接受任意数量的函数。现在我们可以按照我们选择的任何顺序对任意数量的效果进行排序。在这里,我们也跳过了"装饰器的中间创建"而是定义"装饰"根据{{1}}
compose

是的,因为const original = x =>
x * x
const add1 = x =>
x + 1
const compose = (f, ...fs) => x =>
f === undefined
? x
: f (compose (...fs) (x))
const decoratedA =
compose (add1, original, add1)
const decoratedB =
compose (add1, add1, add1, original, original)
const decoratedC =
compose (decoratedB, decoratedA)
console.log (original (4)) // 16 4 * 4
console.log (decoratedA (4)) // 26 ((4 + 1) * (4 + 1)) + 1
console.log (decoratedB (4)) // 259 ((4 * 4) * (4 * 4)) + 1 + 1 + 1
console.log (decoratedC (4)) // 456979 (((((4 + 1) * (4 + 1)) + 1) * (((4 + 1) * (4 + 1)) + 1)) * ((((4 + 1) * (4 + 1)) + 1) * (((4 + 1) * (4 + 1)) + 1))) + 1 + 1 + 1
返回 new 函数,我们甚至可以制作其他作品的作品。甚至使用compose
来构造像console.log
这样的副作用函数,以保证输出与输入相匹配
effect
下方允许我们通过在返回最终值之前将结果记录到logger
来可视化任何特定功能的影响 - 为此,您可以通过添加日志记录行为来说console
装饰 logger (f)
- 但它只是经典的函数组合
f

如果您正在使用类和方法编写OO风格,那并不重要; const square = x =>
x * x
const add1 = x =>
x + 1
const compose = (f, ...fs) => x =>
f === undefined
? x
: f (compose (...fs) (x))
const effect = f => x =>
(f (x), x)
const logger = f =>
compose (effect (console.log), f)
const main =
compose (logger (add1), logger (square))
console.log (main (4))
// 16 (console.log side effect)
// 17 (console.log side effect)
// => 17 (return value)
仍然是您的首选
compose

答案 2 :(得分:2)
在第一个示例中,instance.run
的当前值保留在func
已关闭的变量中,然后为instance.run
分配了一个新值::
instance.run = <old function>
func = instance.run
instance.run = <new function>
// func === <old function> here
因此,当instance.run
调用func
时,它实际上会调用<old function>
。
只需关闭func
中的get()
,就可以在没有IIFE的情况下执行相同操作:
class Dummy {
run() {
console.log('run');
}
}
function get() {
let instance = new Dummy();
let func = instance.run;
instance.run = function() {
func();
console.log('decorator run');
}
return instance;
}
let obj = get();
obj.run();
在第二个片段中,instance.run
旧值丢失,它会有效地调用自身,导致堆栈溢出。