You are on page 1of 22

Open in app

Search Medium

Get unlimited access to all of Medium for less than $1/week. Become a member

10 Concepts to Improve Your Mastery of JavaScript


Sodiq Akanmu · Follow
10 min read · Jan 12

Listen Share More

Photo by Gabriel Heinzer on Unsplash

JavaScript is unarguably one of the most popular programming languages in the world and is
used to create interactive websites and web applications. For developers, learning JavaScript
can lead to a wealth of opportunities due to its strength and versatility. In this article, we will
explore 10 key concepts that can help you improve your JavaScript mastery and take your
skills to the next level. These concepts will offer you a strong foundation in the language and
assist you in developing into a more competent and confident developer.

1.Promises
A powerful tool for handling asynchronous actions in your code is JavaScript promises. They
give you a means to deal with the outcomes of an asynchronous operation, like a network
request, without blocking the execution of the rest of your code. A promise can be in one of
three states — fulfilled, rejected, or pending — and it symbolises the final result of an
asynchronous operation.

In comparison to employing callback functions, promises offer a mechanism to handle the


outcomes of an asynchronous action in a more streamlined and controlled manner.
Additionally, they make it easier to handle errors in a consistent way and chain several
asynchronous tasks together.

Promises are used extensively in JavaScript libraries and frameworks, such as AngularJS,
ReactJS, and Node.js. Introduced in ES6, they are a fundamental concept for modern
JavaScript development. Your code’s readability, maintainability, and performance can all be
considerably enhanced by knowing how to use promises correctly.

In using JavaScript promises, the “promise” object is a constructor that creates a new promise
and takes a single argument, which is a function called the "executor". The executor function
takes two parameters, “resolve” and “reject” which can be used to indicate whether the
asynchronous operation has succeeded or failed.

Let’s look at the following example:

1 const promise = new Promise((resolve, reject) => {


2 // some asynchronous operation
3 if (success) {
4 resolve(value);
5 } else {
6 reject(error);
7 }
8 });

promises1.js hosted with ❤ by GitHub view raw

Once a promise is created, you can use its “then” method to attach callbacks that will be
invoked when the promise is resolved, and its “catch” method to attach callbacks that will be
invoked when the promise is rejected. Alternatively, you can manage several promises
simultaneously by using the Promise.all() and Promise.race() methods. Promises are
available in all modern JavaScript environments and are supported in all major browsers, so
you can use them with confidence in your projects.

2. Async/Await
Just like promises, the “async/await ”feature is used in JavaScript to handle asynchronous
operation. It was introduced in ECMAScript 2017 (ES8) and built on top of promises to make
asynchronous code in JavaScript more manageable and easier to read. Async/await offers a
technique for creating asynchronous code with synchronous appearance and behaviour.

An asynchronous function is defined by the “async” keyword. The asynchronous function can
then yield a promise and use the “await” keyword to delay code execution while it waits for
the promise to be fulfilled.

An example is shown below:

1 const getData async() => {


2 const response = await fetch('https://some-api.com/data');
3 const data = await response.json();
4 return data;
5 }

async1.js hosted with ❤ by GitHub view raw

In the code above, “fetch” is an asynchronous function that returns a promise and the “await ”
keyword is used to wait for the promise to resolve before parsing the JSON data.

Using async/await makes your code to be easier to comprehend, maintained, and be more
robust. And as compared to callbacks and promises, it is a more modern, clearer, and
effective way to write asynchronous code.

That being said, it is crucial to note that async/await complements promises, not replaces
them. Simply put, it is a different syntax to perform the same operation. It still relies on
promises to handle the async flow internally.

3. Closures
In JavaScript, inner functions can access the variables and scope of their outer functions
thanks to a key concept called closures. When a function is defined inside another function
and the inner function retains access to the variables and execution scope of the outer
function even after the outer function has completed its execution, a closure is generated.

Consider the following code:


1 function outerFunction(x) {
2 return function innerFunction() {
3 return x;
4 };
5 }
6
7 const myClosure = outerFunction(10);
8 console.log(myClosure()); // returns 10

closure.js hosted with ❤ by GitHub view raw

In this example, the variable x is accessible to the inner function innerFunction from the outer
function’s scope because it is defined inside the outer function outerFunction. The outer
function returns the inner function, which is then assigned to the myClosure variable. The
inner function still has access to the value of x and can return it when called even when the
outer function’s execution is complete.

Closures in JavaScript are commonly used for a variety of tasks, including the creation of
private variables, the application of functional programming paradigms, and the creation of
closures in loops. By enabling functions to carry data with them, closures can also be utilised
to build robust and expressive code. Also in JavaScript, it can be used to implement object-
oriented programming patterns.

Nonetheless, closures can also result in memory leaks if not used appropriately, as the inner
function continues to maintain a reference to the variable and scope of the outer function
and the garbage collector is unable to release the memory.

4. Prototypal Inheritance
In contrast to the class-based inheritance model used in languages like Java and C#,
JavaScript uses a prototype-based model of inheritance. In JavaScript, objects inherit their
properties and methods from other objects rather than from classes.

Each JavaScript object has an internal property called __proto__ (or [[Prototype]]), which
points to a different object known as the “prototype object.” When an object’s properties or
methods are accessed, JavaScript first determines whether the object itself has the property
or method defined directly on it. If it doesn’t, JavaScript checks the object’s prototype object
and keeps searching through the prototype chain until it either finds the property or method
or reaches the end of the chain.

Let’s look at the example below:


1 const animal = {
2 eats: true
3 };
4
5 const dog = {
6 barks: true
7 };
8
9 dog.__proto__ = animal;
10 console.log(dog.eats); // returns true

prototypal.js hosted with ❤ by GitHub view raw

You will observe from the code above that the “dog” object in this example inherits the “eats”
attribute from “animal”, its prototype object, rather than having it specified directly on it.
This is how prototypal inheritance works. It is a mechanism wherein objects can inherit the
properties and methods from other objects.

It is essential to note here that the inheritance in this context is dynamic and not static,
meaning that you can modify the prototype object by adding or removing properties and
methods, and the objects that derive from it will update to reflect the changes.

Although the prototype-based inheritance paradigm in JavaScript has the potential to be


more flexible and potent than class-based inheritance, it can also be more complicated and
difficult to comprehend. To properly use and create objects in JavaScript, one must have a
solid understanding of prototype-based inheritance.

5. Currying
Currying in JavaScript is a technique that enables partial application of a function by pre-
filling some of its arguments. This allows function calls to be reused and flexible.

Consider a function that accepts the two arguments x and y and returns their total as an
example.

1 function add(x, y) {
2 return x + y;
3 }
4 console.log(add(2, 3)); // 5

currying1.js hosted with ❤ by GitHub view raw

Using closures, we can change this function into a curried function that accepts the
remaining argument.
1 function add(x) {
2 return function(y) {
3 return x + y;
4 }
5 }
6 const add2 = add(2);
7 console.log(add2(3)); // 5

currying2.js hosted with ❤ by GitHub view raw

In this illustration, the initial use of add(2) yields a new function that accepts the final
argument, y. Since the x and y values are already set to 2 and 3, respectively, when add2(3) is
called, the result is 5.

The spread operator and arrow functions can also be used to achieve currying. Let’s take a
look at an example below:

1 const add = (x, y) => (...args) => x + y + args.reduce((a, b) => a + b, 0);


2 const add2 = add(2);
3 console.log(add2(3, 4, 5)); // 14

currying3.js hosted with ❤ by GitHub view raw

In this illustration, the first call to add(2) produces a new arrow function that adds x and y to
any remaining arguments, regardless of their number.

Currying can increase the re-usability and readability of code while also allowing function
calls to be more flexible.

6. Higher-Order Functions
Higher-order functions in JavaScript are those that accept one or more functions as
arguments and/or return another function as their output. As they can be used as any other
variable or value, these functions are also referred to as “first-class functions.” The term
“higher-order” indicates a higher of level of abstraction than the usual JavaScript function.

Some examples of higher-order functions are:

i. Array.prototype.map(): This function applies a callback function to each element of an


array it receives as an argument, returning a new array with the results.

ii. Array.prototype.filter(): This function accepts a callback function as an argument and


applies it to each element of the array, returning a new array with only the elements that pass
the test.
iii. Array.prototype.reduce(): This function takes a callback function as an argument and
applies it to each element of an array, accumulating the results into a single value.

iv. Array.prototype.forEach(): This function is an iterative method, and calls a provided


callback function once for each element in an array in ascending-index order.

v. setTimeout(): This function accepts a callback function as an input and schedules its
execution for a given amount of time in the future.

vi. Promise.then(): This function accepts a callback function as an input and schedules its
execution for when a promise is resolved.

These higher-order functions offer a mechanism to abstract logic and functionality by


passing them around as variables or arguments, allowing for more adaptable and reusable
code. For instance, if you want an array of numbers to only return even numbers, you can use
the higher-order method filter().

1 const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];


2 const evenNumbers = numbers.filter(number => number % 2 === 0);
3 console.log(evenNumbers); // [2, 4, 6, 8, 10]

higherOrderFunction1.js hosted with ❤ by GitHub view raw

The filter() function in this example receives an anonymous function as a callback and uses it
to determine whether each element in the array is even before returning only the even
numbers.

7. Generators
These are a special type of function in JavaScript that allow for execution pauses and restarts.
They are helpful when working with iterators and asynchronous programs since they let you
construct a sequence of values over time.

A generator function is defined using the function* keyword and uses the yield keyword to
pause execution and return a value. When the generator function is invoked, it produces an
iterator object that can be used to control the generator’s execution.

Assuming we want to create a generator function that returns the next number in the
Fibonacci sequence:
1 function* fibonacci() {
2 let [a, b] = [0, 1];
3 while (true) {
4 yield a;
5 [a, b] = [b, a + b];
6 }
7 }
8
9 const fib = fibonacci();
10 console.log(fib.next().value); // 0
11 console.log(fib.next().value); // 1
12 console.log(fib.next().value); // 1
13 console.log(fib.next().value); // 2

generator.js hosted with ❤ by GitHub view raw

In the above example, the function* keyword is used to declare the generator function
fibonacci(). It uses the yield keyword to return the current value of a and updates the values
of a and b using destructuring assignment. The while loop ensures that the generator will
continue to produce new values indefinitely.

The generator function, when invoked, returns an iterator object that can be used to control
the generator’s execution. The next() method is used to resume execution and return the next
value.

Large data sets, asynchronous code, iterators, and infinite sequences can all be implemented
with the help of generators. Without having to load all the data into memory at once, they
enable you to generate a succession of values over time.

8. WeakMaps and WeakSets


Similar to a Map in JavaScript, a WeakMap is a data collection type that lets you store key-
value pairs. The important distinction is that keys in a WeakMap are weakly referenced,
which means they can be garbage collected if no other references to them exist. WeakMap
can thus be used to store data that you don’t want to keep in memory if it is no longer being in
use.

On the other hand, a WeakSet is similar to a Set but exhibits the same weak referencing
behaviour for its components. This means that if there are no other references to an element
in a WeakSet, it can be garbage collected.

Here’s an example of using a WeakMap in JavaScript:


In the example above, we created a WeakMap and store a key-value pair with an object
serving as the key. We then use the object as the key to obtain the value. The key-value pair is
likewise eliminated from the WeakMap when we set the object to null, making it suitable for
garbage collection.

An example of a WeakSet, however, is shown below:


In this example, two objects are added to a WeakSet that has been created. The has() method
is then used to determine whether a certain object is present in the set. The first object gets
taken out of the WeakSet and is eligible for garbage collection after we set it to null.

Both WeakMap and WeakSet are helpful for managing JavaScript memory usage because they
let you store information without storing it in memory permanently.

9. Proxies
Objects that serve as intermediaries between the code that accesses them and the underlying
objects they represent are known as proxies. They give you the ability to modify and intercept
operations made on the underlying object, such as method calls, constructors, and property
access.
The following code is an example of how a proxy can be used to intercept property access:

In this example, an object called obj is wrapped in a Proxy object. To handle property access
and assignment, we define two “handler” functions, “get” and “set”. We can intercept and
change the behaviour of the underlying object using these functions. The “get” function will
be called, a message will be logged, and the value of the “name” property from the original
object will be returned when we access the name property of the proxy object.

Proxies can be used for various purposes, such as:

Implementing custom property accessors

Implementing custom method invocation


Implementing custom constructors

Implementing custom behaviour for “virtual” objects

Implementing custom behaviour for existing objects

Implementing custom error handling

Implementing custom security features

You can also use Proxies to create “virtual” objects that do not have a direct representation in
memory, such as lazy-loading properties or data from an API.

10. Reflect API


Similar to how you would use the JavaScript’s built-in operators and functions, the JavaScript
Reflect API offers a number of methods that let you carry out various actions on objects. The
primary distinction is that built-in operators and functions often return the operation’s result,
whereas the Reflect API methods typically return a Boolean indicating the operation’s success
or failure.

Let’s take a look at the example below:


In this example, the “name” property on the object obj is set to the value “John” using the
Reflect.set() method. When the operation becomes successful, the method returns true.

Here is another example of how to use the Reflect API to determine whether or not an object
is extensible:
This example shows how to check the extensibility of an object using the
Reflect.isExtensible() method. Due to its extensible nature by default, it initially returns true.
Following that, we use Object.preventExtensions(obj) to stop the object from being extended.
Next, we use Reflect.isExtensible(obj) once more, which returns false to indicate the object is
no longer extensible.

The Reflect API provides a number of methods, such as:

Reflect.get()

Reflect.set()

Reflect.has()
Reflect.deleteProperty()

Reflect.defineProperty()

Reflect.getOwnPropertyDescriptor()

Reflect.getPrototypeOf()

Reflect.setPrototypeOf()

Reflect.preventExtensions()

Reflect.isExtensible()

Reflect.apply()

Reflect.construct()

Reflect API is typically used when you want to produce more robust and maintainable code or
when you want to conduct operations on an object in a more consistent and predictable
manner.

In conclusion, mastering JavaScript necessitates having a solid grasp of a wide range of


concepts and techniques. The concepts discussed in this article, such as closures, prototypes,
higher order functions are just a few of the many that are essential for understanding how
JavaScript works and how to write efficient and effective code. Additionally, understanding
how to use modern features like Promises, async/await, WeakMap and WeakSet, Proxies and
Reflect API, which are also discussed here, can help you write more modern and powerful
JavaScript code. By taking the time to fully understand these concepts and how they work,
you can become a more proficient and confident JavaScript developer.

JavaScript Javascript Tips Javascript Development Modern Syntax Advanced Javascript


Follow

Written by Sodiq Akanmu


152 Followers

Coding artisan and educator, dedicated to crafting elegant and innovative applications that merge form with function.

More from Sodiq Akanmu

Sodiq Akanmu

Do More with Less: 10 Advanced JavaScript Syntax to Write Clean and Efficient
Code with Brevity
I received great feedback from readers of one of my most recent articles, “10 concepts to improve your
mastery of JavaScript”, regarding…

10 min read · Jan 21

352 4
Sodiq Akanmu

10 Ways to Become A Better Software Engineer


Software engineering is currently one of the most in-demand occupations worldwide. In addition to
receiving a great salary, specialists in…

2 min read · Jan 1

116 5

Sodiq Akanmu
The 10 Deadly Sins of JavaScript Programming: Common Errors You Must Avoid
Have you ever spent hours debugging your JavaScript code only to find out that the problem was really
just a simple oversight that you…

13 min read · May 8

55 1

Sodiq Akanmu

JavaScript Casing Conventions: A Must-Know for Every Developer


Casing conventions are the unsung heroes of programming; they sometimes go unnoticed, yet they have
a significant influence on how readable…

5 min read · Feb 9

105 1

See all from Sodiq Akanmu

Recommended from Medium


Rehan Pinjari

7 Front-end Development Trends to Follow in 2023


Front-end web development is nowhere. Here’s everything you need to know about WebDev in 2023.

5 min read · Feb 3

326 7

Sodiq Akanmu
Do More with Less: 10 Advanced JavaScript Syntax to Write Clean and Efficient
Code with Brevity
I received great feedback from readers of one of my most recent articles, “10 concepts to improve your
mastery of JavaScript”, regarding…

10 min read · Jan 21

352 4

Lists

Stories to Help You Grow as a Software Developer


19 stories · 45 saves

Staff Picks
310 stories · 77 saves

Çağlayan Yanıkoğlu in Jotform Tech

8 Performance Optimization Techniques You Should Know in React


Hey! I’m Çağlayan Yanıkoğlu and I work as a front-end developer at Jotform. I’m also a front-end
instructor/consultant at Patika.dev and…

9 min read · Jan 29

1K 5
FardaKarimov in JavaScript in Plain English

Preventing Double Execution of useEffect in React: Tips and Tricks


One of the core features of React is its useEffect hook, which allows you to perform side effects in your
components. However, it’s…

4 min read · Mar 23

94 1

Rehan Pinjari in Dev Genius


Structure Your React Apps Like It’s 2030
Every React Developer meets one issue during his or her journey.

8 min read · Mar 10

985 18

Avinash Vagh

Ultimate Roadmap To Become Full Stack Developer in 2023


There are many types of developers exist

12 min read · Dec 19, 2022

411 4

See more recommendations

You might also like