JavaScript Overview — Understanding Functions
Functions in JavaScript (also as in any other programming language) is an important concept that allows us to write a code to use it later than it was written.
Functions help us use one of fundamental software design principle — DRY (Don’t repeat yourself).
In JS functions are “first class objects”. It’s means everything that objects have they have too.
Executing a function is called invoking, calling, or applying it.
Parameters
Using parameters for functions we can generalize the functions to make them reusable.
Note: Parameters to a function behave like regular bindings, but their initial values are given by the caller of the function, not the code in the function itself.
We can define default parameters when declaring a function.
More:
Execution Context
When the JavaScript engine executes a script for the first time (we can think like we execute one big function that contains our code), it creates the global execution context. During this phase, the JavaScript engine performs the following tasks:
- Create the global object i.e.,
window
in the web browser orglobal
in Node.js. - Create the
this
object and bind it to the global object. - Setup a memory heap for storing variables and function references.
- Store the function declarations in the memory heap and variables within the global execution context with the initial values as
undefined
.
For each invocation of a function from this point the JavaScript engine creates a new function execution context comprising:
- The thread of execution (we go through the code in the function line by line)
- A local memory where anything defined in the function is stored
Note: At runtime only one global execution context but multiple function (local) contexts.
We can think that “context” means as a space (box) in which to execute a code. See example below.
Call Stack
Call Stack is part of JavaScript engine and engine uses Call Stack to manage execution contexts: the Global Execution Context and Function Execution Contexts.
Call Stack is a structure (principle of Last In, First Out) that JavaScript engine uses to keep track of function invocations. So every time you invoke a function engine places that on the Call Stack. And keep doging so including functions that you invoke recursively and once the invocation is complete engine is going to pop one function at a time and execute it.
It’s very important to understand this because you only get one Call Stack. So if you will keep Call Stack busy your application is busy.
Notes from MDN:
- When a script calls a function, the interpreter adds it to the call stack and then starts carrying out the function.
- Any functions that are called by that function are added to the call stack further up, and run where their calls are reached.
- When the current function is finished, the interpreter takes it off the stack and resumes execution where it left off in the last code listing.
- If the stack takes up more space than it had assigned to it, it results in a “stack overflow” error:
RangeError: Maximum call stack size exceeded
More:
- Call stack in JavaScript
- Understanding JavaScript Functions, Execution Context, and the Call Stack
- Closures in Javascript: Inside the Execution Context & Scope with Codesmith
Let’s look at one example
In our code we create function called multiply
and then create a variable called twoByTwo
witch is result of invocation of multiply(2, 2)
So let’s figure out what happen under the hood after we run this code.
When we run our code JavaScript Engine adds “global” function in Call Stack which creates Global Execution Context. Inside this Global Execution Context creates Local Memory.
Then it parse our code line-by-line and declaring variable multiply
in Global Memory (NOTE: we don’t know what inside this function until we will invoke it, it also means JavaScript will not go inside and pars this function until we will invoke this function). Then it goes to the next line where we declaring variable twoByTwo
and the value of this variable is result of multiply(2,2)
(NOTE: Until this function executing our twoByTow
don’t have any value, we don’t never notice it but it’s what really happens).
Now JavaScript look at Scope Memory (it’s Global Memory) and trying to find multiply
and it’s a function. So it will invoke it (NOTE: if our multiply
refers to not a function JavaScript will throw an error: multiply is not a function
).
After that our function add in Call Stack and Local Execution Context for multiply(2,2)
creates. Inside this context also creates Local Memory and JavaScript start parsing code inside multiply
function. It’s declaring result
variable inside Local Memory with value 2*2
and it’s 4
then our function return result
. Here JavaScript also look at Scope Memory (it’s Local Memory) and trying to find result
(NOTE: if JavaScript will not find result
in Local Memory it will try to find it in Parent Scope Memory (it’s Global Memory) and if no result
there JavaScript will throw an error: is not defined
). In our case JavaScript find it in Local Scope and simply return it.
Then JavaScript Engine removesmultiply(2,2)
from Call Stack and Local Execution Context for multiply(2,2)
automatically destroyed. And JavaScript can assign return value from multiply(2,2)
which is 4
to twoByTow
variable.
Since it was the last line of code and nothing to do our CallStack will clear from global
function and Global Execution Context destroyed too and program exit. So everything removes.
Closure
Every time when you invoke a function new local (function) memory creates. And when a function finishes executing, it’s local memory is deleted. (except a return value).
But what if our function can remember some data between invocations? That will let our function have associated cache/persistent memory. Let’s look at example bellow:
As we learned before after counter
function finished execution all local memory removed, except a return value: increment
. But increment
function need count
to work with it. So where it will take it? Here is a trick!
As soon as we declare increment
under the hood JavaScript engine immediately added a hidden property [[scope]]
to it.
It means function “hold” all data that function is referred to with it, we can think about it as a “backpack”. So if our increment
function don’t use unusedVariable
it will never add this variable in “backpack”.
Where you define a function determines what variables a function have access to when you call a function.
Closure (“backpack”) is when a function remembers and continues to access variables from outside its scope, even when the function is executed in a different scope.
I’m highly recommending to watch this video: Closure, Scope & Execution Context by Will Sentance to fully understand this topic.
More:
- The Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript
- Variable scope, closure
- Deep dive into Scope Chains and Closures
this
Context is dynamic and dependent on how it is called (regardless of where it is defined or even called from).
We need to understand that context and scope are not the same. Every function invocation has both a scope and a context associated with it.
As we learned before scope pertains to the variable access of a function when it is invoked and is unique to each invocation. Context is always the value of the this
keyword which is a reference to the object that “owns” the currently executing code.
this
is not a fixed characteristic of a function based on the function’s definition, but rather a dynamic characteristic that’s determined each time the function is called.
In most cases, the value of this is determined by how a function is called (runtime binding). It can’t be set by assignment during execution, and it may be different each time the function is called. ES5 introduced the bind() method to set the value of a function’s this regardless of how it’s called, and ES2015 introduced arrow functions which don’t provide their own this binding (it retains the this value of the enclosing lexical context).
More:
- What is this?
- What is THIS in JavaScript?
- Understanding Scope and Context in JavaScript
- Understanding the “this” keyword, call, apply, and bind in JavaScript
Arrow Functions
Arrow functions, or Fat arrow functions are frequently used in callback chains, promise chains, array methods, in any situation where unregistered functions would be useful.
There are differences between arrow functions and traditional functions, as well as some limitations:
- Arrow functions don’t have their own bindings to
this
,arguments
orsuper
, and should not be used as methods. - Arrow functions don’t have access to the
new.target
keyword. - Arrow functions aren’t suitable for
call
,apply
andbind
methods, which generally rely on establishing a scope. - Arrow functions cannot be used as constructors.
- Arrow functions cannot use
yield
, within its body.
When using arrow functions, we must be careful of the scope that these functions are called in. Arrow functions follow normal scoping rules in JavaScript, with the exception of the this
scope. Recall that in basic JavaScript, each function is assigned a scope, that is, the this
scope.
Arrow functions are not assigned a this scope. They inherit their parent’s this
scope and cannot have a new this
scope bound to them. This means that, as expected, arrow functions have access to the scope of the parent function, and subsequently, the variables in that scope, but the scope of this cannot be changed in an arrow function.
Using the .apply()
, .call()
, or .bind()
function modifiers will NOT change the scope of an arrow function’s this
property. If you are in a situation where you must bind this
to another scope, then you must use a normal JavaScript function.
More: