Walk me through what happens between writing a JavaScript function and it running.
The pipeline has four stages, and the first two are not what most people expect. Your source text is parsed into a tree that describes the shape of the program. That tree is then compiled into bytecode, and the bytecode is what the interpreter runs first. Only after a function proves that it is used often does the engine promote it to machine code with a just-in-time compiler.
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5, and now the engine may optimise itTwo useful details come from the parsing step. The parser is also where syntax errors are reported, so a misplaced brace never reaches the running code. The engine also collects information about each function while it parses, such as which variables the function closes over, and that is how it can set up the right shape of environment without running the code at all.
The promotion to machine code is what makes the language fast in practice. The just-in-time compiler guesses the types it has already seen, copies small functions into their callers, and removes branches that can never run. When one of those guesses turns out to be wrong at runtime, the engine throws the fast version away and goes back to the slower path, which is why a function that behaves in one way for months and then suddenly meets a new type can get slower all at once.
Precompilation and streaming compilation exist because of this pipeline. A browser can parse ahead of execution while the script is still downloading, and a server can compile a module to bytecode, and sometimes to machine code, while the build is running.