A closure is an advanced concept that involves a function and any other data the function can access. So, a Closure is a function that uses variables from the outer lexical scope. The interpreter considers any arguments you pass to functions from the global space. If a function only relies on its internal values and parameters, it's not considered a closure. Remember that functions can access values from other external functions considered closures.
The interpreter stores that data in Heap Memory, calling the function and knowing the free variables' values. That also means they require more memory and processing power. Closures are robust and have many advantages. They help with data encapsulation. Plus, they also help with removing redundant and maintaining modular code. Let's see an example:
2. JavaScrpit Inheritances
You might also have heard the term prototypal inheritance. The Prototype Chain explains that all objects have a private property called "[[Prototype]]" that allows objects to inherit properties from each other.
Both the browser and Node.js are constantly running a single-threaded event loop. That means they execute only one line of code at a time. It's easier to picture it if you imagine a circle. The browser and Node.js repeat the process, checking for code execution.
Things get spicy here because sometimes developers deliberately queue tasks. So, the browser executes them on the next event. The event loop checks for pending tasks and runs them in a specific order. Thanks to this mechanism, the browser can execute tasks in a non-blocking way, which is handy since modern websites have many things going on.
These functions are great for handling asynchronous operations. The interpreter will give you the results of every function in the order they appear, starting from the top of the file and going downwards. However, if a function takes a long time to complete its task, the next one will execute first. That might be different from what you expected when you wrote the functions. You can quickly solve that by passing the first function as a parameter to the next one. And that's a callback function!
You'll often see callback functions where the first involves a lengthy task, usually fetching data from an API. That's why some people use setTimeout(), but we prefer to keep it as simple as possible. But bear in mind that you'll fall into callback hell if you overuse them or nest too many. You can easily avoid that by using promises to accomplish the same results.
Software developers use async to define an asynchronous function. These are perfect for involving many iterations, such as fetching data from an API or reading a file from a disk. Asynchronous functions will automatically return a promise, but you can pause their execution using the await keyword. This way, the function will wait for some other promises to resolve. That can improve readability and error handling. Let's keep with our coffee example!
There's no chance you haven't heard of functional programming. This trendy programming paradigm encourages using only pure functions. That also means that you must avoid using mutability and side effects. In fact, apart from pure functions, immutability is essential in functional programming.
A function that takes one or multiple functions as parameters or returns a function is a high-order function. They're pretty standard in functional programming. Like any other function, you can pass them as values, which favors reusability. That also makes your code more concise and declarative. Let's see some examples:
Reduce is a powerful method that takes an array of elements to reduce them by applying a function to each element. It accumulates all the elements and returns a single value.
The Map function allows you to modify each element of an array returning a new identical array. You can also accomplish this by using for loops or nesting. Map() provides a more elegant way to do it following the functional programming rules.
This function can filter an array according to a particular condition and returns a new array with the elements that passed the condition. Remember that the original stays as is since it returns a new array.
The sort() function allows you to overwrite an array by sorting its elements. If it's an array of integers, it'll sort it in ascending order by default. On the flip side, if it's an array of strings, it will sort it alphabetically. What if you don't want to sort an array in alphabetical or ascending order, you may ask? You can easily sort arrays in non-alphabetical or descending order by combining sort() with reverse(). So after sorting the list, you have to do listname.reverse() to reverse its order.
You can think of generators as special functions that you can pause and resume. Plus, they provide a new way to interact with iterators and regular functions. Instead of producing all values simultaneously, they create them as a sequence on the fly.
This concept will definitely blow your mind if you come from another language. Hoisting allows you to declare variables and functions after their assignment. It has this name because it is as if the interpreter hoists those variables and functions to the top of the scope. This way, it executes the code with no errors. You can only take advantage of this using the function and var keywords. If you use cons or let, the interpreter will not hoist the variables or functions you declare. Let's show you how that works with a quick example:
Invoked Function Expressions (IIFEs) are functions you don't store in variables. Plus, they don't receive a name, either. Hence, IIFEs just run after you call them. They can be handy and improve your code's quality. By using closures, you avoid declaring variables on the global scope. That's one of their most popular uses. Let's now see a quick example:
Memoization is one of the essential topics for building top-performing web apps. On top of that, you're very likely to deal with questions related to it in tech coding interviews. When building large web applications, software developers use complex functions. As you can imagine, they can take a while to load. Sometimes, they receive many calls to return the same value several times. That can be highly inefficient.
That's when memoization catches the values based on the arguments. This way, when the function receives another call, it gives the result instantly. That's how they improve performance. Memoization is a fundamental topic of Dynamic Programming, so you'll see it a lot in React.js.