JavaScript装饰模式。错误:超出了最大调用堆栈大小

时间:2018-01-26 09:17:02

标签: javascript recursion decorator

这是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 仍然是原始方法的包装器,没有“无用的”附加自执行功能。

我很高兴听到答案

3 个答案:

答案 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旧值丢失,它会有效地调用自身,导致堆栈溢出。