JavaScript Overview — Understanding Functions

Vlad Antsitovich
8 min readJun 2, 2022

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 or global 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.

Call Stack Example

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:

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:

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:

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 or super, 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 and bind 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:

--

--