Back to Blog

12 Advanced JavaScript Concepts

30
May
2023
12 Advanced JavaScript Concepts

So you learned JavaScript's basic concepts and want to take your skills to the next level? Well, let me tell you something, you've made the right choice! JavaScript has been the most used programming language for quite a few years. JavaScript has a 78% usage rate among developers. Its popularity surpasses even Python, Java, HTML, and CSS. Mastering advanced JavaScript will allow you to build more complex websites without frameworks. Plus, whenever you decide what framework to learn, it'll be much easier for you.

I'm guessing you've been grinding on the basic JavaScript concepts for a while and can easily use them. I'm discussing variables, data types, control flow, functions, arrays, and objects. Are we on the same page? Perfect! So, grab your favorite drink (coffee, ideally), and sit tight because we're about to become experienced developers!

12 Advanced JavaScript Concepts

1. JavaScript Closures

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:

function createCaffeineAddict(coffeeType) {
const addiction = `addicted to ${coffeeType}`;
return function getHigh() {
const highMessage = `Feeling wired and ${addiction}!`;
console.log(highMessage);
};
}
const espressoJunkie = createCaffeineAddict('espresso');
const latteLover = createCaffeineAddict('latte');
espressoJunkie();
latteLover();

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.

In JavaScript, objects also inherit methods from other objects, thanks to this. Some data types like Strings, Numbers, and Arrays inherit valuable methods. When searching for a property or a method, the interpreter will try to find a matching name on the object. If it can't find it, it'll also seek the object's property and even the property of the property. That's how it goes until it reaches the end of the chain. Following our example, we can see the inheritance like this:

function createCaffeineAddict(coffeeType) {
const addiction = `addicted to ${coffeeType}`;
return function getHigh() {
const highMessage = `Feeling wired and ${addiction}!`;
console.log(highMessage);
};
}

const espressoJunkie = createCaffeineAddict('espresso');
const latteLover = createCaffeineAddict('latte');
espressoJunkie();
latteLover();

3. JavaScript Event Loops

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.

4. JavaScript Callback Functions

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!

function weightDisplayer(someWeight) {
document.getElementById("display").innerHTML = someWeight + " grams";
}

function calculateTotalWeight(arabicaWeight, robustaWeight, myCallback) {
let totalWeight = arabicaWeight + robustaWeight;
myCallback(totalWeight);
}

// Calculate the total weight of 500 grams of Arabica and 250 grams of Robusta coffee beans
calculateTotalWeight(500, 250, weightDisplayer);

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.

5. JavaScript Async/Await And Promises

Both Async and Await are unique keywords that modify functions in JavaScript. "async and await make promises easier to write." Promises are an essential JavaScript aspect to understanding Async and Await. They are objects representing a value that will be available in the future. Hence, they say their value is "pending." Now that we established that, let's get back to Async and Await.

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!

async function prepareCoffee() {
try {
const beans = await fetch('https://example.com/api/beans');
const groundBeans = await grindBeans(beans);
const brewedCoffee = await brewCoffee(groundBeans);

return brewedCoffee;
} catch (error) {
console.error(error);
}
}

const myCoffee = prepareCoffee();
console.log(myCoffee);

6. JavaScript Functional Programming

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.

Following these practices sounds tiresome. Yet, the benefits far outweigh the trouble. You must also embrace high-order functions. Don't worry if you don't know what they are. We'll cover that in a sec. First, check this example of functional programming in JavaScript.

const coffee = {
type: 'Arabica',
roast: 'dark',
hasCaffeine: true,
aroma: {
notes: ['chocolate', 'nutty', 'spicy']
}
};

// Pure function that returns a new object with a modified aroma const addAromaNote = (coffeeObj, note) => ({
...coffeeObj,
aroma: {
...coffeeObj.aroma,
notes: [...coffeeObj.aroma.notes, note]
}
});

const newCoffee = addAromaNote(coffee, 'fruity');
console.log(newCoffee.aroma.notes); // ['chocolate', 'nutty', 'spicy', 'fruity']
console.log(coffee.aroma.notes); // ['chocolate', 'nutty', 'spicy']

7. JavaScript High-Order Functions

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:

function brew(coffeeMaker, coffeeType) {
return coffeeMaker(coffeeType);
}

function makeAmericano(coffeeAmount) {
return `Brewing ${coffeeAmount} ml of Americano...`;
}

function makeLatte(coffeeAmount) {
return `Steaming ${coffeeAmount} ml of milk for Latte...`;
}

const result1 = brew(makeAmericano, 200); // returns "Brewing 200 ml of Americano..."
const result2 = brew(makeLatte, 300); // returns "Steaming 300 ml of milk for Latte..."

JavaScript has a few built-in higher-order functions developers use all the time. They help to perform complex operations, and are essential to interact with frameworks like React, Vue, and Angular. Let's see some of the most popular ones!

A. JavaScript Reduce()

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.

B. JavaScript Map()

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.

C. JavaScript Filter()

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.

D. JavaScript Sort()

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.

8. JavaScript Generators

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.

That might not sound very clear, so let's show you how they work with a cool example. In JavaScript, you can create functions using the function* syntax. Then you can use the keyword yield to stop the function and return a value to the user.

function* coffeeGenerator() {
const coffeeTypes = ['latte', 'cappuccino', 'espresso', 'americano'];
for (let i = 0; i < coffeeTypes.length; i++) {
yield coffeeTypes[i];
}
}

const coffee = coffeeGenerator();
console.log(coffee.next().value); // latte
console.log(coffee.next().value); // cappuccino
console.log(coffee.next().value); // espresso
console.log(coffee.next().value); // americano

Generators don't produce those values simultaneously. They're much more memory-efficient than JavaScript arrays. Hence, you might want to use them to iterate over large datasets. Remember that you might use them less often while working as a web developer. However, it's vital to have them in your tool belt.

9. JavaScript Hoisting

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:

var coffeeBlend;
console.log(coffeeBlend);
coffeeBlend = "Dark Roast";

// Output: undefined
brewCoffee();
function brewCoffee() {
console.log("Brewing a fresh pot of coffee!");
}

// Output: "Brewing a fresh pot of coffee!"

10. JavaScript IIFEs

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:

(function(coffeeType) {
console.log("Brewing a fresh pot of " + coffeeType + " coffee!");
})("French roast");
// Output: "Brewing a fresh pot of French roast coffee!"

11. JavaScript Memoization

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.

// Define a function to memoize
function multiply(x, y) {
return x * y;
}

// Define a memoization cache as an object
const cache = {};

// Define a memoized version of the function
function memoizedMultiply(x, y) {
const cacheKey = x + ":" + y;
if (cache[cacheKey] !== undefined) {
return cache[cacheKey];
} else {
const result = multiply(x, y);
cache[cacheKey] = result;
return result;
}
}

// Call the memoized function
console.log(memoizedMultiply(2, 3)); // Should print "6"
console.log(memoizedMultiply(2, 3)); // Should print "6" again (result is already memoized)

12. JavaScript Currying

Currying transforms a function that receives many arguments into a sequence of functions. These new functions will only receive one argument. That's what currying can do for you! This powerful technique has that name because of Haskell Brooks Curry, a famous mathematician and logician, and the concept of currying comes from Lambda Calculus. Let's go back to how you can use currying in JavaScript.

function brewCoffee(beans) {
return function(water) {
return beans * water;
}
}
const coffeeMaker = brewCoffee(10);
// returns a function that multiplies its argument by 10 (the number of coffee beans)
console.log(coffeeMaker(8)); // prints 80 (the amount of water)
console.log(coffeeMaker(12)); // prints 120 (the amount of water)

Final Thoughts

The topics covered in this blog post will give you the foundation to build robust web apps. Plus, they will improve your understanding of the JavaScript code essence. In this manner, you'll be an industry expert, and it will lead you to learn popular frameworks more easily. Make sure you let each concept solidify before you move on to the next one, as they can be hard to grasp if you are unfamiliar. Grab another cup of coffee, and happy developing!