简体   繁体   English

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

[英]JavaScript Decorator pattern. Error: Maximum call stack size exceeded

Here is a working example of Decorator pattern: 这是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();

However, if we change the get function to: 但是,如果我们将get函数更改为:

function get() {
    let instance = new Dummy();
    instance.run = function() {
        instance.run();
        console.log('decorator run');  
    }       

    return instance;
}

we will be faced with an error: VM68418:6 Uncaught RangeError: Maximum call stack size exceeded at Dummy.instance.run (:6:32) 我们将面临一个错误: VM68418:6 Uncaught RangeError:Dummy.instance.run(:6:32)超出了最大调用堆栈大小

Why is this happening? 为什么会这样? The instance.run is still a wrapper around the original method, without 'useless' additional self executed function. instance.run仍然是原始方法的包装器,没有“无用的”额外的自执行功能。

I will be glad to hear the answer 我很高兴听到答案

instance.run()在其自己的定义中被调用,因此它导致永不结束的递归,因此导致超出最大调用堆栈大小的错误。

I believe it's dangerous to get tangled in made-up things like "decorators", "decorator pattern", or even "patterns". 我认为纠结于“装饰者”,“装饰者模式”甚至“模式”之类的制作品是危险的。 At the core of your issue, you have a function whose behaviour you wish to alter, or decorate ... 在您的问题的核心,您有一个功能,您希望改变或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 

So with decorate , we're capturing this incrementing + 1 effect, but notice we were forced to decide when to increment; 因此,通过decorate ,我们捕获这个递增+ 1效果,但请注意我们被迫决定何时递增; before or after the original function was called. 调用原始函数之前之后 Maybe in a different variation, we want to "decorate" using this +1 effect but at the opposite time. 也许在不同的变体中,我们想要使用这个+1效果“装饰”,但在相反的时间。

Below, firstAdd1 is a "decorator" that increments before the original function is called. 下面, firstAdd1是一个“装饰器”, 调用原始函数之前递增。 thenAdd1 is a decorator that increments after the original function is called. 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) 

But now we've sort of duplicated the +1 effect. 但现在我们有点重复+1效果。 "Decorating" as it turns out, is just function composition . 事实证明,“装饰”只是功能构成 Realizing this, we remove pain and suffering from our program. 意识到这一点,我们消除了我们的计划带来的痛苦和痛苦。

Below, we capture our +1 effect in a pure function, add1 , and then simply compose it before or after a given f 下面,我们在纯函数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)

No objects were harmed in the making of this program 在制作这个节目时没有任何对象受到伤害

 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) 

Of course function composition is massively powerful. 当然,功能组合非常强大。 We can modify compose to accept an arbitrary number of functions. 我们可以修改compose以接受任意数量的函数。 Now we can sequence any number of effects in any order of our choosing. 现在我们可以按照我们选择的任何顺序对任意数量的效果进行排序。 Here, we also skip the intermediate creation of "decorators" and instead define "decorated" functions directly in terms of 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 

Yep, because compose returns a new function, we can even make compositions of other compositions. 是的,因为compose返回一个新的函数,我们甚至可以组成其他组合。 Even compose side-effecting functions like console.log using effect which guarantees the output matches the input 甚至使用effect来组合诸如console.log effect函数,以保证输出与输入匹配

Below logger allows us to visualize any particular function's impact by logging the result to the console before returning the final value – to this end, you could say logger (f) decorates f by adding a logging behaviour – but it's just classical function composition 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) 

If you're writing OO-style with classes and methods, it doesn't matter; 如果你正在用类和方法编写OO风格,那没关系; compose is still your go-to 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! 

In the first example, the current value of instance.run is retained in the func closed variable, then instance.run is assigned a new value:: 在第一个示例中, instance.run的当前值保留在func closed变量中,然后为instance.run分配一个新值::

instance.run = <old function>
func = instance.run
instance.run = <new function>
// func === <old function> here

So when instance.run invokes func , it essentially invokes <old function> . 因此,当instance.run调用func ,它基本上调用<old function>

You can do the same without IIFE, by simply closing func in get() : 只需关闭get() funcget()可以在没有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(); 

In the second snippet, instance.run old value is lost and it effectively invokes itself causing a stack overflow. 在第二个片段中, instance.run旧值丢失,它有效地调用自身,导致堆栈溢出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM