DEV Community

Cover image for New JavaScript features you might have missed
Derick Ify Iloabachie
Derick Ify Iloabachie

Posted on

New JavaScript features you might have missed

Introduction

Overview of JavaScript

JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. While it is most well-known as the scripting language for Web pages, many non-browser environments also use it, such as Node.js, Apache CouchDB and Adobe Acrobat. JavaScript is a prototype-based, multi-paradigm, single-threaded, dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles.

Importance of keeping up with new JavaScript features

  • Improved efficiency and performance
  • Better developer experience
  • Improved job prospects
  • Improved interoperability with other technologies

This article provides an overview of some new features of JavaScript that you might not be familiar with which can help boost productivity.

Private class features

By default, class fields are public. However, private class members can now be created using a hash # prefix.

Private members were not native to JavaScript before this syntax existed

class ClassWithPrivate {
  #privateField;
  #privateFieldWithInitializer = 'cat';

  #privateMethod() {
    // - -
  }

  static #privateStaticField;
  static #privateStaticFieldWithInitializer = 42;

  static #privateStaticMethod() {
    // - -
  }
}
Enter fullscreen mode Exit fullscreen mode

Some things worth noting are :

  • All private identifiers within a class must be unique
  • The private identifier cannot be #constructor

Static initialization blocks

Static initialization blocks are a special feature of a class that enable more flexible initialization of static properties than can be achieved using per-field initialization.

Static blocks allow statements to be evaluated during initialization, which allows initialization that (for example) include try...catch or set multiple fields from a single value.

class ClassWithStaticInitializationBlock {
  static staticProperty1 = 'Property 1';
  static staticProperty2;
  static {
    this.staticProperty2 = 'Property 2';
  }
}

console.log(ClassWithStaticInitializationBlock.staticProperty1);
// Expected output: "Property 1"
console.log(ClassWithStaticInitializationBlock.staticProperty2);
// Expected output: "Property 2"
Enter fullscreen mode Exit fullscreen mode

Some things worth noting are :

  • A class can have any number of static {} initialization blocks in its class body
  • var declarations in the block are not hoisted. Variables declared inside the static block is local to the block.
var y = "Outer y";

class A {
  static field = "Inner y";
  static {
    var y = this.field;
  }
}

// var defined in static block is not hoisted
console.log(y); // 'Outer y'

Enter fullscreen mode Exit fullscreen mode

Top level await

You can use the await keyword on its own (outside of an async function) at the top level of a module. This means that modules with child modules that use await will wait for the child modules to execute before they themselves run, all while not blocking other child modules from loading.

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

// Waits for timeout - no error thrown
await setTimeoutAsync(5000);

// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());

export default await colors;

Enter fullscreen mode Exit fullscreen mode

Error cause

The cause data property of an Error instance indicates the specific original cause of the error.

It is useful in catching and re-throwing an error with a more-specific or useful error message in order to still have access to the original error.

try {
  connectToDatabase();
} catch (err) {
  throw new Error('Connecting to database failed.', { cause: err });
}

Enter fullscreen mode Exit fullscreen mode

The value of cause can be of any type.

in operator check for private fields

The in operator returns true if the specified property is in the specified object or its prototype chain

// old way 
class C {
  #x;
  static isC(obj) {
    try {
      obj.#x;
      return true;
    } catch {
      return false;
    }
  }
}

// new way
class C {
  #x;
  static isC(obj) {
    return #x in obj;
  }
}

Enter fullscreen mode Exit fullscreen mode

This generally avoids the need for dealing with error handling just to access a private property that may be nonexistent. However, the in operator still requires the private property to be declared beforehand in the enclosing class — otherwise, it would throw a SyntaxError

at() for indexing

The at() method takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array or string.

const array1 = [5, 12, 8, 130, 44];

let index = 2;

console.log(`Using an index of ${index} the item returned is ${array1.at(index)}`);
// Expected output: "Using an index of 2 the item returned is 8"

index = -2;

console.log(`Using an index of ${index} item returned is ${array1.at(index)}`);
// Expected output: "Using an index of -2 item returned is 130"
Enter fullscreen mode Exit fullscreen mode

The at() method is equivalent to the bracket notation when index is non-negative. For example, array[0] and array.at(0) both return the first item. However, when counting elements from the end of the array, you cannot use array[-1] because all values inside the square brackets are treated literally as string properties.

The usual practice is to access length and calculate the index from that — for example, array[array.length - 1]. The at() method allows relative indexing, so this can be shortened to array.at(-1).

// array with items
const cart = ["apple", "banana", "pear"];

// A function which returns the last item of a given array
function returnLast(arr) {
  return arr.at(-1);
}

// Get the last item of array 'cart'
const item1 = returnLast(cart);
console.log(item1); // 'pear'

// Add an item to our 'cart' array
cart.push("orange");
const item2 = returnLast(cart);
console.log(item2); // 'orange'
Enter fullscreen mode Exit fullscreen mode

Bonus

Logical AND (&&)

Read on and you might just be impressed with what you would learn about logical operators return values.

The logical AND (&&) (logical conjunction) operator for a set of boolean operands will be true if and only if all the operands are true. Otherwise it will be false.

More generally, the operator returns the value of the first falsy operand encountered when evaluating from left to right, or the value of the last operand if they are all truthy.

a1 = true && true; // t && t returns true
a2 = true && false; // t && f returns false
a3 = false && true; // f && t returns false
a4 = false && 3 === 4; // f && f returns false
a5 = "Cat" && "Dog"; // t && t returns "Dog"
a6 = false && "Cat"; // f && t returns false
a7 = "Cat" && false; // t && f returns false
a8 = "" && false; // f && f returns ""
a9 = false && ""; // f && f returns false
Enter fullscreen mode Exit fullscreen mode

Logical OR (||)

The logical OR (||) (logical disjunction) operator for a set of operands is true if and only if one or more of its operands is true. It is typically used with boolean (logical) values. When it is, it returns a Boolean value. However, the || operator actually returns the value of one of the specified operands, so if this operator is used with non-Boolean values, it will return a non-Boolean value.

true || true; // t || t returns true
false || true; // f || t returns true
true || false; // t || f returns true
false || 3 === 4; // f || f returns false
"Cat" || "Dog"; // t || t returns "Cat"
false || "Cat"; // f || t returns "Cat"
"Cat" || false; // t || f returns "Cat"
"" || false; // f || f returns false
false || ""; // f || f returns ""
false || varObject; // f || object returns varObject

Enter fullscreen mode Exit fullscreen mode

Note: If this operator is used to provide a default value to some variable, be aware that any falsy value will not be used.

Browser compatibility issues

When using new JavaScript features, it's important to consider browser compatibility because not all browsers may support the latest features.

Here are some things to keep in mind:

  • Browser compatibility matrix: You can check the browser compatibility matrix for the latest JavaScript features on websites like caniuse.com or the MDN web docs
  • Polyfills and transpilation: If a new JavaScript feature is not supported by a specific browser, you can use a polyfill or transpile your code to a version of JavaScript that is compatible with the target browser.

In summary, when using new JavaScript features, it is important to consider browser compatibility and take the necessary steps to ensure your code works in a variety of browsers. This may involve using polyfills, transpilation, feature detection, or a combination of these approaches.

Conclusion

This article has served as an overview of new features added to the JavaScript programming language. You learned about some new features and the benefits of staying up to date with JavaScript which you can leverage when working on a new (or existing project).

In my opinion, keeping up with cutting-edge technologies can dramatically improve productivity.

Top comments (0)