DEV Community

Cover image for JavaScript 2024: Latest Features, Performance, Modern Techniques, and Advanced Coding 🚀
abdulnasır olcan
abdulnasır olcan

Posted on

JavaScript 2024: Latest Features, Performance, Modern Techniques, and Advanced Coding 🚀

JavaScript is an essential language in modern web development. Over time, it has evolved to become the foundation for both front-end and back-end applications. In this article, we will explore the modern features JavaScript offers in 2024, including coding techniques and important concepts we've covered previously, all with detailed examples.

We'll cover topics valuable to both beginners and experienced developers, discovering how JavaScript helps us write cleaner, more performant, and readable code.

  1. Modern JavaScript Syntax for Clean and Readable Code (ES2020) JavaScript has become simpler with new features like optional chaining, nullish coalescing, and arrow functions.

Optional Chaining & Nullish Coalescing (ES2020)
In the past, we had to check if each property of an object existed. In modern JavaScript, optional chaining (?.) has made this process much simpler. At the same time, nullish coalescing (??) allows us to assign default values to null or undefined variables.

a. Optional Chaining (ES2020)
The optional chaining operator (?.) lets us access an object’s properties or sub-properties without manually checking for their existence, especially useful in deep object structures to prevent errors.



const user = {
  name: 'Abdulnasır',
  info: {
    email: 'abdulnasirolcan@example.com'
  }
};

console.log(user.info?.address); // undefined

console.log(user.info?.address?.city); // undefined

const email = user?.info?.email ?? 'Email not provided';
console.log(email); // Result: 'abdulnasirolcan@example.com'


Enter fullscreen mode Exit fullscreen mode

b. Nullish Coalescing (Nullish Birleştirme Operatörü) (ES2020)

The ?? operator allows assigning a default value only when the value is null or undefined. It does not apply to other falsy values such as 0, false, or ''.



const userInput = '';
const name = userInput ?? 'Default Name';
console.log(name);

const userInput2 = null;
const name2 = userInput2 ?? 'Default Name';
console.log(name2); // Result: 'Default Name'


Enter fullscreen mode Exit fullscreen mode
  1. Logical Assignment Operators (ES2021) In 2024, logical assignment operators have become widely used, simplifying complex expressions with shorter and more effective logic. Operators like &&=, ||=, and ??= allow for more concise and readable value assignments, streamlining conditional logic in JavaScript.


let name = 'Abdulnasır';

name ||= 'Default Name';
console.log(name); // Result: 'Abdulnasır'

let age = 0;

age ??= 30;
console.log(age); // Result: 0 (not nullish)


Enter fullscreen mode Exit fullscreen mode


let isActive = true;
let userName = null;

isActive &&= false;
console.log(isActive); // Result: false

userName ??= 'Guest';
console.log(userName); // Result: 'Guest'

userName ||= 'Abdulnasır';
console.log(userName); // Result: 'Guest'


Enter fullscreen mode Exit fullscreen mode


let x = 0;

// ||=
x ||= 5;
console.log(x); // Result: 5 0 falsy)

// ??=
x ??= 10;
console.log(x); // Result: 5 (0 not nullish)


Enter fullscreen mode Exit fullscreen mode
  1. Pipeline Operator (|>) - Proposed New Feature (ES2024) The pipeline operator (|>) is a proposed feature that hasn't been fully integrated into JavaScript yet, but if accepted, it could be a major game changer. The |> operator allows one function's output to be passed directly into another function in a pipeline-like structure. This simplifies multi-step processes by making function composition more readable and maintainable.


const double = x => x * 2;
const increment = x => x + 1;

const result = 5 
  |> double 
  |> increment;

console.log(result); // Result: 11


Enter fullscreen mode Exit fullscreen mode

Here, the number 5 is first passed into the double function and then into the increment function. This allows us to perform multi-step processes in a cleaner and more understandable way.

  1. Record & Tuple: Immutable Data Structures (ES2024) Record and Tuple are immutable data structures added to JavaScript, providing more secure and efficient data management, especially in large projects.

a. What is a Record? (ES2024)

A Record is similar to JavaScript objects but immutable. Once created, its contents cannot be modified, making it useful for data security and writing more error-free code.



const userRecord = #{ name: 'Abdulnasır', age: 30 };

// Record immutable
// userRecord.name = 'Mehmet'; // Error

console.log(userRecord.name); // Result: 'Abdulnasır'


Enter fullscreen mode Exit fullscreen mode

b. What is a Tuple? (ES2024)

A Tuple is an immutable array. Similar to normal JavaScript arrays, but once a Tuple is created, it cannot be modified. This makes Tuples a useful structure for managing constant data and ensuring immutability in code.



const tuple = #[1, 2, 3];

// Tuple immutable
// tuple.push(4); // Error

console.log(tuple[0]); // Result: 1


Enter fullscreen mode Exit fullscreen mode

The use of Record and Tuple enhances data security and optimizes performance in large, complex projects. These structures address the need for immutable data structures in JavaScript.

  1. Top-Level Await (ES2022) Top-level await allows the use of await directly within modules without needing an async function. This is particularly useful for waiting for asynchronous operations to complete before the module loads, simplifying asynchronous code in modular systems.


const data = await fetch('https://api.example.com/data');
const jsonData = await data.json();
console.log(jsonData);


Enter fullscreen mode Exit fullscreen mode
  1. WeakRefs and FinalizationRegistry (ES2021) These new features allow us to create weak references to an object in memory, enabling us to check if the object has been cleaned up by the garbage collector. This is particularly useful for large data or systems that use weak references, providing better memory management and control in JavaScript applications.


let object = { name: 'Weak reference object' };
const weakRef = new WeakRef(object);

console.log(weakRef.deref()); // { name: 'Weak reference object' }

setTimeout(() => {
  console.log(weakRef.deref());
}, 1000);


Enter fullscreen mode Exit fullscreen mode
  1. Arrow Functions (ES2015) Arrow functions provide a shorter and more concise way to define functions. A key advantage of arrow functions is that they preserve the this context, which is particularly useful in functions that are nested or passed as callbacks. This makes them a powerful tool for cleaner and more efficient coding in modern JavaScript.


const multiply = (a, b) => a * b;
console.log(multiply(2, 3)); // Result: 6


Enter fullscreen mode Exit fullscreen mode

These modern features both speed up the coding process and improve readability.

  1. Managing Asynchronous Operations: Promises, async/await, and Promise.allSettled() (ES2015/ES2020) Asynchronous operations are very common in web development, particularly for managing long-running processes like API calls. JavaScript has made handling these operations more readable with the introduction of Promises and async/await.

a. Promises and async/await (ES2015)

The best way to handle asynchronous code in a synchronous manner is by using async and await. This allows us to write cleaner code without chaining multiple .then functions.



async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}


Enter fullscreen mode Exit fullscreen mode

b. Promise.allSettled() (ES2020)

When managing multiple Promise operations, Promise.allSettled() waits for all the promises to complete, regardless of whether they succeed or fail. This makes it easier to handle multiple asynchronous operations simultaneously, as it provides a way to get the results of each promise, including those that fail, without short-circuiting like Promise.all().



const promise1 = Promise.resolve('Data 1');
const promise2 = Promise.reject('Error in Data 2');
const promise3 = Promise.resolve('Data 3');

Promise.allSettled([promise1, promise2, promise3]).then(results => {
  results.forEach(result => {
    console.log(result.status === 'fulfilled' ? result.value : result.reason);
  });
});


Enter fullscreen mode Exit fullscreen mode

c. Promise.any() (ES2021)

Promise.any() returns the result of the first promise to successfully resolve from a given set of promises. This method allows you to focus on whichever promise succeeds first, without failing the entire operation if one or more promises reject. It’s particularly useful when you're interested in the success of any promise and don’t need all promises to complete successfully.



const p1 = Promise.reject('Error 1');
const p2 = Promise.resolve('Success 1');
const p3 = Promise.resolve('Success 2');

Promise.any([p1, p2, p3])
  .then(result => {
    console.log(result); // Result: 'Success 1'
  })
  .catch(error => {
    console.log(error);
  });


Enter fullscreen mode Exit fullscreen mode

9. Simplifying Error Management: Beyond try-catch (ES2020)

Error management is always crucial. Traditional try-catch blocks can sometimes become overly nested, making code harder to read. However, with async/await, error handling can be made cleaner. Additionally, creating helper functions allows for even simpler error management.

Error Management with Helper Functions (ES2020)

In the example below, a to() function manages asynchronous operations using try-catch and returns both the error and result as a tuple (array).

a. Error and Data Management

This pattern simplifies handling both errors and results in one step.



async function to(promise) {
  try {
    const result = await promise;
    return [null, result];
  } catch (error) {
    return [error, null];
  }
}

async function fetchData() {
  const [error, data] = await to(fetch('https://api.example.com/data'));
  if (error) return console.error('Fetch error:', error);
  console.log(data);
}


Enter fullscreen mode Exit fullscreen mode

b. Error and Data Management



async function to(promise) {
  try {
    const data = await promise;
    return [null, data];
  } catch (error) {
    return [error, null];
  }
}

async function fetchData() {
  const [networkError, response] = await to(fetch('https://api.codingbeautydev.com/docs'));
  if (networkError) return console.error('Network Error:', networkError);

  const [parseError, data] = await to(response.json());
  if (parseError) return console.error('Parse Error:', parseError);

  return data;
}


Enter fullscreen mode Exit fullscreen mode

Helper Function (to): This function separates the error and result of a promise into an array ([err, data]). This allows direct error checking, making the code cleaner and avoiding deeply nested try-catch blocks.

Network and Parse Error Handling: The to function simplifies error management for both the fetch operation and JSON parsing, making the code more readable and easier to maintain.

10. Efficient Array Operations: map, filter, reduce (ES2015)

JavaScript’s functional programming capabilities make working with arrays easier. Methods like map, filter, and reduce allow complex operations on arrays in a concise manner.

a. Processing Arrays with map() and filter() (ES2015)

map() is used to transform each item in an array, while filter() selects items that meet specific conditions.



const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(num => num * 2);
console.log(doubled); // Result: [2, 4, 6, 8, 10]

const filtered = numbers.filter(num => num > 3);
console.log(filtered); // Result: [4, 5]


Enter fullscreen mode Exit fullscreen mode

b. Reducing an Array to a Single Value (ES2015)

reduce() is used to reduce an array to a single value. It’s perfect for tasks like summing, multiplying, or performing more complex calculations.



const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // Result: 15


Enter fullscreen mode Exit fullscreen mode

These functions make your code shorter and more readable when working with complex arrays.

11. Performance Optimization: Lazy Loading, Debouncing, Throttling (ES2020)

To improve the user experience, optimizing JavaScript performance is crucial. Techniques like lazy loading, debouncing, and throttling can significantly speed up applications, especially when working with large datasets and DOM manipulations.

a. Delaying Image Loading with Lazy Loading (ES2020)

Lazy loading ensures that images are only loaded when they are about to appear in the user's viewport, improving page load times significantly.



<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazy" />

<script>
  document.addEventListener("DOMContentLoaded", function() {
    const lazyImages = document.querySelectorAll('img.lazy');
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.classList.remove('lazy');
          observer.unobserve(img);
        }
      });
    });
    lazyImages.forEach(img => observer.observe(img));
  });
</script>


Enter fullscreen mode Exit fullscreen mode

b. Improving Performance with Debouncing and Throttling

Debouncing and throttling are used to control frequent events. For instance, instead of sending an API request on every keystroke in a search bar, these techniques ensure the API call is only made after the user has finished typing. This reduces unnecessary requests and enhances performance by minimizing the load on both the client and the server.



function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(event) {
  console.log('Fetching search results for:', event.target.value);
}, 300));


Enter fullscreen mode Exit fullscreen mode

12. The Future of JavaScript: BigInt, WeakRef, and Temporal API (ES2021/ES2024)

In 2024, JavaScript continues to evolve with new features that stand out. BigInt allows for working with numbers larger than 2^53 - 1, overcoming the limitations of regular JavaScript numbers. WeakRef improves memory management by creating weak references to objects that can be cleaned up by the garbage collector. The proposed Temporal API simplifies working with dates and times, offering a more precise and powerful way to handle temporal data than the standard Date object.

a. Working with Large Numbers Using BigInt

JavaScript typically operates safely within the 2^53 - 1 range. To handle larger numbers, BigInt is used.



const bigNumber = BigInt('9007199254740992');
console.log(bigNumber + 1n); // 9007199254740993n


Enter fullscreen mode Exit fullscreen mode

b. Working with Time and Dates Using the Temporal API

JavaScript's built-in Date class can sometimes be limiting in terms of accuracy and complexity. The Temporal API is a proposed new feature that addresses these issues by providing a more precise and powerful way to handle time and date operations. With Temporal, you can work with time zones, calendar systems, and durations in a more reliable and efficient manner, making it ideal for modern applications that require accurate date and time manipulations.



const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // Result: 2024-10-06


Enter fullscreen mode Exit fullscreen mode


const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // Result: 2024-10-05

const birthday = Temporal.PlainDate.from('1990-05-15');
const duration = today.since(birthday);
console.log(Result: ${duration.years});


Enter fullscreen mode Exit fullscreen mode

13. Private Class Fields (ES2021)

In JavaScript, class properties can be made private by using the # symbol before the property name. This ensures that the property is not accessible outside of the class, improving data encapsulation and security. Private class fields are a critical feature for maintaining data privacy within objects, as they prevent external code from directly accessing or modifying sensitive data, reinforcing the principle of information hiding in object-oriented programming.



class Person {
  #name; // Private Field

  constructor(name) {
    this.#name = name;
  }

  getName() {
    return this.#name;
  }
}

const abdulnasir = new Person('Abdulnasır');
console.log(abdulnasir.getName()); // Result: 'Abdulnasır'
console.log(abdulnasir.#name); // Error


Enter fullscreen mode Exit fullscreen mode

14. String.replaceAll() (ES2021)

The String.replaceAll() method allows you to replace all occurrences of a substring within a string. This makes it much easier to modify multiple instances of a character or pattern at once. Unlike replace(), which only changes the first match, replaceAll() ensures that every match of the specified substring or regular expression is replaced, making string manipulation tasks much simpler, especially when dealing with repetitive text replacements.



const text = 'Hello world! The world is wonderful!';
const newText = text.replaceAll('world', 'planet');
console.log(newText); // Output: 'Hello planet! The planet is wonderful!'


Enter fullscreen mode Exit fullscreen mode

15. Intl.RelativeTimeFormat (ES2020)

Intl.RelativeTimeFormat is useful for expressing how long ago or how soon a date is relative to the current time. It provides a more natural way to present time-based information to users.



const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // Output: 'yesterday'
console.log(rtf.format(1, 'day'));  // Output: 'tomorrow'



Enter fullscreen mode Exit fullscreen mode

16. Array.flatMap() (ES2019)

The flatMap() method simplifies array processing by both flattening the array (removing nesting) and performing a map operation in a single step. This allows for more concise transformations of arrays.



const numbers = [1, 2, 3, 4];
const doubledAndFlattened = numbers.flatMap(n => [n, n * 2]);
console.log(doubledAndFlattened); // Result: [1, 2, 2, 4, 3, 6, 4, 8]


Enter fullscreen mode Exit fullscreen mode

Conclusion

JavaScript has evolved into an indispensable language for modern applications. With the new features introduced by 2024, it enables writing cleaner, more efficient, and performant code. Asynchronous processes, functional arrays, modern error handling, and performance optimizations maximize the potential of JavaScript.

The examples we've discussed in this article showcase how JavaScript's new and powerful features can help you build more effective solutions in your projects. By practicing more, you can explore these advantages and develop better applications.

Happy coding! 🚀

Top comments (0)