JavaScript 装饰器模式。错误:超出最大调用堆栈大小
以下是 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: Maximum call stack size reached at Dummy.instance.run (:6:32)
为什么会发生这种情况? instance.run 仍然是原始方法的包装器,没有“无用”的额外自执行函数。
我很高兴听到答案
instance.run()
在其自身定义内被调用,因此导致永无止境的递归,进而导致
超出最大调用堆栈大小
错误。
我认为纠结于“装饰器”、“装饰器模式”甚至“模式”等虚构的东西是很危险的。问题的核心是,你有一个函数,你想改变它的行为,或者
装饰
...
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
因此,使用
装饰
,我们捕获了这种递增的
+ 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 效果。事实证明,“装饰”只是 函数组合 。意识到这一点,我们就可以消除程序中的痛苦和折磨。
下面,我们在纯函数
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
以接受任意数量的函数。现在我们可以按任意顺序对任意数量的效果进行排序。在这里,我们还跳过了“装饰器”的中间创建,而是直接根据
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
是的,因为
compose
返回一个
新
函数,我们甚至可以将其他组合组合起来。甚至可以使用
effect
组合诸如
console.log
之类的副作用函数,以确保输出与输入匹配
下面的
logger
允许我们通过在返回最终值之前将结果记录到
console
来可视化任何特定函数的影响 - 为此,您可以说
logger
(f)
通过添加日志记录行为来装饰
f
- 但这只是经典的函数组合
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)
如果您使用类和方法编写 OO 样式,这并不重要;
compose
仍然是您的首选
const compose = (f, ...fs) => x =>
f === undefined
? x
: f (compose (...fs) (x))
const effect = f => x =>
(f (x), x)
const addExcitement = x =>
x + '!'
const capitalize = x =>
x.toUpperCase ()
class Person {
constructor (name) {
this.name = name
}
greet () {
return `I am ${this.name}`
}
}
// "decorator"
const Shouter =
effect (m =>
m.greet = compose (addExcitement, capitalize, m.greet.bind(m)))
const p = new Person ('me')
console.log (p.greet ()) // I am me
Shouter (p)
console.log (p.greet ()) // I AM ME!
在第一个示例中,
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>
。
您可以在没有 IIFE 的情况下执行相同的操作,只需在
get()
中关闭
func
即可:
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
的旧值丢失,并且它有效地调用自身,导致堆栈溢出。