JavaScript: how it works
how does this language work?
Before going through the following, this article covers
the JavaScript Engine
the interpreter that compiles JS code into machine language
the JavaScript Runtime
the JS Engine user and provider of additional functionalities
Let's try to understand how JavaScript works with the help of some very nicely written articles from JavaScript Tutorial:
Execution context
What actually happens when we declare variables and functions, as well as call them?
When we declare a variable as such:
...the "JavaScript engine" uses an execution context made up of two phases in order to:
declare the existence of the variable
x
(the creation phase)so this variable actually begins with a value of
undefined
setup a space in memory (the memory heap) to store this variable
finally assign
x
a value (in this case,10
) (the execution phase)
(...the article also explains a smaller-scale version of execution contexts within local scopes)
Call stack
When we have a lot of execution contexts, leap-frogging from here to there and everywhere, what gets called first? Well, the JavaScript engine uses the call stack to ensure priority:
When we create a new local execution context, we add ("push") it to the call stack
When we add another context on top of that, we stack that on top of the call stack
When we finish with the latest context, we remove ("pop") it from the call stack
The last context on the stack gets popped off first, until all contexts get popped off
"Last in, first out" (LIFO)
Event loop
If everything is all so LIFO in the call stack, then JavaScript can do only one thing at a time! So, how do we get it to multi-task? Some functions might take a very long time to execute (think of a file download) and this can cause blockage! Just how do we make things more efficient?
By using special functions called callback functions, we can
let the web browser make use of other components (e.g. the Web API and callback queue)
allow some "slower" functions to by-pass the call stack of the JavaScript runtime!
Hoisting
When we declare global variables, all variables act as though they appeared (hoisted) at the top of the code:
Functions then get hoisted above the variables, so the code behaves in a rearranged fashion as such:
function declarations
variable declarations
function calls
Scoping
So, where does a variable live?
Global scope
A variable not inside any
function
exists in the global scopeWe can access this variable from anywhere in the script
On a browser, this scope is the entire browser
window
Local (lexical) scoping
A variable inside a
function
will exist only inside its curly bracesWe cannot use this variable from outside of the braces!
We can only take its value outside if
something in the global scope calls the function
the function includes the value in its
return
statement
Block scoping occurs when
We declare a variable with the
let
keyword inside curly bracesthis includes
if
blocks
Global scoping versus local scoping
If a variable appears in the global scope and then appears in a local scope, then what happens?
We would expect the first
console.log
to return1
the
return x
there refers to the x inside the local scope ofk()
the value of
x
inside the brackets!
We would then expect the second
console.log
to return2
the console has no idea that the
x
inside ofk()
existsit has no memory of the first log
only the global
x
exists!
So, as soon as another x
gets declared inside a local scope, a variable such as x
takes on a new context!
"Memory leaks"
As discussed in the article, weird stuff happens when we assign a value to a variable that we never declared (or "declare without a keyword"):
In this case, the JavaScript engine:
looks for the
counter
variable in the local scopefinds none
looks for the
counter
variable in the global scopefinds none
creates a
counter
variable in the global scope (!!!)
In order to avoid this "weird problem" of memory leaks into the global scope, we would append this even weirder string literal at the top of the code, called use strict
:
Closuring
Now, another weird thing:
A function has a closure when it can access a variable outside of its scope - in this example, cat
is available via this
in printInfo
:
Yet, a nested function within a parent function won't have access to the variables accessible by the parent function! Instead, the nested function gets bound to the global scope (!!)
The article then explains a way to mitigate by using a helper variable:
apply(), bind() and call()
Of course, the article also lets us know the existence of three other built-in JavaScript functions: call()
, apply()
and bind()
that make the code a little cleaner!
So, in the above, call()
, apply()
and bind()
all do the same thing and print out the same result but:
apply(thisArg, [var1, var2, ..., varn])
can take on more variables within an array as the second argument
n
is however many arguments the function
bind(thisArg, var1, var2, ..., varn)
can take on multiple variables in multiple arguments
allows the changing of what
this
referencesallows storage in another variable for use
call(thisArg, var1, var2, ..., varn)
takes on multiple variables in multiple arguments
basically
calledFunction()
becomescalledFunction.call(this)
with thethis
In all three functions, the first argument consists of what this
means, while the other arguments map onto the parametric signature for the nested function!
Let's look at this example for clarity:
Such a paradigm allows us to create functions in more abstract ways for more robust code re-use!
Further reading
Conclusion
So those concepts form some of the nuances of how JavaScript works behind the browser window!
Last updated