If I define a function
inc = function(x) { return x + 1 }
and make a nested invocation of it
inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(inc(1)))))))))))))))))))))
this will result in the value 22
. If I revise the nested expression to instead make use of call
, passing in null
for this
, as
inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, 1)))))))))))))))))))))
this will also produce the value 22
.
But, on JavaScriptCore this second form appears to consume O(2^n) memory, where n is the number of nested calls. This is not the case if I try this JavaScript in Firefox or Chrome so it seems to be isolated to JavaScriptCore.
I have very little JavaScript experience (almost none). I don't have a feel for the tradeoffs that various JavaScript implementations might make, nor whether it is reasonable for the example code to be expensive in some implementations (providing generic support for closures or some such), while efficient in others.
My question is: Is this code inherently problematic? Should it be rewritten to be structured in a different way? Or is the code fine—does JavaScriptCore simply have a bug?
I've done some experimentation where refactoring a few of the inner calls to temporaries will "truncate" the memory doubling behavior
var temp1 = inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, 1)))))));
var temp2 = inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, temp1)))))));
inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, inc.call(null, temp2)))))));