17

If I write this in global scope:

(function(){})();

is the anonymous function created when the statement is executed and destroyed immediately after the statement is executed?

if I write this in a function:

function foo()
{
    var a=1;
    (function(){})();
    a++;
}

Does the anonymous function exist until foo returns, or just exist during the execution of that statement?

William
  • 761
  • 2
  • 10
  • 27
  • My first guess is that it get destroyed after the statement is executed by the garbage collector. Because there is no link kept to that piece of code afterwards. I'll wait for answers with proof :) [Look in here](https://www.quora.com/Do-anonymous-Javascript-IIFEs-remain-in-the-memory-stack-after-they-are-used) – Orelsanpls Oct 15 '18 at 08:03
  • 1
    Yes, functions are created and garbage-collected like any other objects. – Bergi Oct 15 '18 at 08:04

2 Answers2

22

In this particular case, most engines will optimize that function entirely away, because it does not do anything.

But let's assume that function contains code and is indeed executed. In this case, the function will exist all the time, either as compiled code, as bytecode or as AST for the interpreter.

The part that won't exist all the time is the scope and the possible created closure. The scope created for that function and the closure will only exist as long as the function is executed or a reference to the function with a specific bound scope/closure exists.

So the combination function reference + scope will be allocated at the time the statement (function(){})(); is executed, and can be released after that statement. But the compiled version of function(){} might still exist in memory for later use.

For engines that do just in time compiling and optimization, a function might even exist in different compiled versions.

The JIT+optimizer part of modern js engines is a complex topic, a rough description of v8 can be found here html5rocks: JavaScript Compilation:

In V8, the Full compiler runs on all code, and starts executing code as soon as possible, quickly generating good but not great code. This compiler assumes almost nothing about types at compilation time - it expects that types of variables can and will change at runtime.

In parallel with the full compiler, V8 re-compiles "hot" functions (that is, functions that are run many times) with an optimizing compiler. [...] In the optimizing compiler, operations get speculatively inlined (directly placed where they are called). This speeds execution (at the cost of memory footprint), but also enables other optimizations.

Therefore it may be that the generated code has hardly any similarities to the original one.

So a immediately-invoked function expression might even be completely optimized away using inlining.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 2
    That information about V8 is ~17 months out-of-date, V8's used a bytecode interpreter for one-off or little-used code since replacing the old Full-codegen+Crankshaft with Ignition+TurboFan ([details](https://v8.dev/blog/launching-ignition-and-turbofan)). (Ignition is the bytecode interpreter.) – T.J. Crowder Oct 15 '18 at 13:49
  • 2
    I also wouldn't be so sure the bytecode for a one-off function isn't tossed, the V8 team have been doing a lot to focus on reducing memory consumption; dropping all information about a function that can never be called again would be low-hanging fruit. – T.J. Crowder Oct 15 '18 at 13:52
  • @T.J.Crowder that's true, I was about to update that the part that the bytecode could and most likely will also be released if it is not used for a while the same way that existing code will be optimized if it is used in a special way. And I will update the V8 info. – t.niese Oct 15 '18 at 14:03
5

If I write this in global scope:

(function(){})();

is the anonymous function created when the statement is executed and destroyed immediately after the statement is executed?

As t.niese said, odds are the engine will optimize that function away entirely. So let's assume it has some code in it:

// At global scope
(function(){ console.log("Hi there"); })();

The engine can't guarantee that that code won't throw an error (for instance, if you replaced console with something else), so I'm fairly sure it can't just inline that.

Now the answer is: It depends.

From a language/specification level, all code in a compilation unit (roughly: script) is parsed when the compilation unit is first loaded. Then, that function is created when the code reaches it in step-by-step execution, executed after being created (which involves creating an execution context for the call), and immediately eligible for garbage collection when done (along with the execution context) because nothing has a reference to it. But that's just theory/high-level specification.

From a JavaScript engine perspective:

  • The function is parsed before any code runs. The result of that parsing (bytecode or machine code) will be associated with that function expression. This doesn't wait for execution to reach the function, it's done early (in the background on V8 [Google's engine in Chrome and Node.js]).
  • Once the function has been executed and nothing else can refer to it:
  • The function object and the execution context associated with calling it are both eligible for GC. When and how that happens depends on the JavaScript engine.
  • Which leaves the function's underlying code, either bytecode (modern versions of V8 using Ignition, possibly others) or compiled machine code (somehow the function got used so much it got compiled for TurboFan, or older versions of V8 using Full-codegen, others). Whether the JavaScript engine can then throw away that bytecode or machine code will depend on the engine. I doubt engines throw away byte/machine code they've generated for a function if they may need it again (e.g., for a new anonymous function created by a new call to foo). If foo became unreachable, maybe foo's byte/machine code and the anonymous function's could be tossed as unnecessary. I have no idea whether engines do that. On the one hand, it seems like low-hanging fruit; on the other, it seems like something that would be so uncommon it's not worth bothering with. (Remember, here we're not talking about code attached to a function instance, but the code that has been produced from the source that gets attached to an instance when the instance is created, and may get attached to multiple instances over time.)

Here are a couple of articles on the V8 blog that make interesting reading:

if I write this in a function:

function foo()
{
    var a=1;
    (function(){})();
    a++;
}

Does the anonymous function exist until foo returns, or just exist during the execution of that statement?

Let's assume again there's a console.log in that function, and that I'm correct (it is an assumption on my part) that the fact it relies on a writable global (console) means it can't just be inlined.

The high-level/specification answer is the same: The function is parsed when the script is loaded, created when it's reached, executed, and eligible for GC when it's done executing. But again, that's just high-level concept.

Things are probably different at the engine level:

  • The code will be parsed before any code in the script runs.
  • Bytecode or machine code is likely generated before any code in the script runs, although I seem to recall something from the V8 blog about parsing but not immediately compiling the contents of top-level functions. I can't find that article, though, if it wasn't just in my head.
  • When execution reaches the function, a function object for it is created along with an execution context (unless the engine is sure it can optimize that away without it being observable in code).
  • Once execution ends, the function object and that execution context are eligible for GC. (They may well have been on the stack, making GC trivial when foo returns.)
  • The underlying code, though, sticks around in memory in order to be used again (and if used often enough, optimized).
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    "*parsing but not immediately compiling the contents of top-level functions*" - it's not just in your head, I remember it too. Not that I could link the article either, of course... – Bergi Oct 15 '18 at 18:36
  • "*The engine can't guarantee that the code won't throw an error, so I'm fairly sure it can't just inline that.*" - why would that be? Because of the stack trace? I doubt an optimising compiler would care - it could still re-establish the stack when an error actually occurs. – Bergi Oct 15 '18 at 18:41
  • "*the function object and the closure context associated with*" would be my first attempt at optimisation. Why create a concrete object that no one uses, and no one can possibly refer to (it's anonymous, it's not assigned to anything, and `arguments.callee` is deprecated)? Sure, we still need the bytecode thing to call and put on the stack, but the object creation should be rather easily avoided. – Bergi Oct 15 '18 at 18:46
  • @Bergi - All sorts of optimization is probably possible. I tend to doubt eliminating a function that would need to be "re-established" for the stack trace is something engines do, but hey, I could be surprised. – T.J. Crowder Oct 16 '18 at 06:55
  • Yeah, you're right, that's too complicated probably. But JS compilers can do inlining, right? I guess it just inlines the code for "put debugging information on the stack" as well - it just doesn't jump to and return from other code. – Bergi Oct 16 '18 at 07:15
  • "functions that can never be reached again": If `foo` can be called again, then the anonymous function can most certainly be reached again. I would expect that SpiderMonkey, V8 and JavaScriptCore know this and don't garbage collect such functions. If not, then it would need to become a best practice to not use anonymous functions in frequently called code. – Inigo Apr 14 '22 at 01:00
  • @Inigo - If you call `foo` again, it's a **different** anonymous function, not the same one. But the comment did make me want to revise the paragraph about bytecode/machine code a bit. – T.J. Crowder Apr 14 '22 at 07:46
  • Yes, it's "different" from language semantics perspective, but not from a parser/lexical perspective and not from a compiled code (whether byte or JIT native) perspective. Its your distinction between lang/spec and engine that makes your answer better than the accepted one! Given the common use of anonymous functions for callbacks and closures in Javascript, I'm not sure it's all that "uncommon", though perhaps not common enough to optimize? Maybe with your pull you can get a V8 or Firefox dev to chime in? – Inigo Apr 14 '22 at 20:02
  • @Inigo - LOL, I have no pull. :-) I like to think I've made that distinction above (better now since your comment), so I think we're okay. – T.J. Crowder Apr 15 '22 at 07:11