Some features that every JavaScript developer should know in 2025
Published on

JavaScript is constantly evolving and newer features are introduced. This oftentimes makes older coding practices outdated, and even less efficient. Bellow is a list of some important features (old and new) that most developers might be unaware of.
Iterator helpers
Have you ever performed multiple chained array transformations on the same array? For example, something like
this:
arr.slice(10, 20).filter(el => el < 10).map(el => el + 5)
. This is really inefficient because
for
each
transformation a new array should be allocated. Imagine you do this for huge arrays (> 500K elements)... This is
why JS has recently introduced methods for iterators. They work similar to regular array transformation methods,
except they don't create temporary arrays but create new iterators which iterate on other iterators. This is the
list
of the methods:
Iterator.prototype.drop()
. Returns a new iterator helper object that skips the given number of elements at the start of this iterator. It roughly does the same thing asArray.prototype.slice(n)
for the regular array.Iterator.prototype.take()
. Returns a new iterator helper object that takes at most the given number of elements from the start of this iterator. It roughly does the same thing asArray.prototype.slice(0, n)
for the regular array.Iterator.prototype.some()
. Is similar toArray.prototype.some()
. It tests whether at least one element produced by the iterator passes the test implemented by the provided function. element.Iterator.prototype.every()
. Is similar toArray.prototype.every()
. It tests whether all elements produced by the iterator pass the test implemented by the provided function.Iterator.prototype.filter()
. Is similar toArray.prototype.filter()
. Returns an iterator on the filtered values.Iterator.prototype.find()
. Is similar toArray.prototype.find()
. Returns the first element produced by the iterator that satisfies the provided testing function.Iterator.prototype.flatMap()
. Is similar toArray.prototype.flatMap()
. Returns an iterator on the flattened values.Iterator.prototype.forEach()
. Is similar toArray.prototype.forEach()
. It executes a provided function once for each element produced by the iterator.Iterator.prototype.map()
. Is similar toArray.prototype.map()
. Returns an iterator of transformed values by a mapping function.Iterator.prototype.reduce()
. Is similar toArray.prototype.reduce()
. It executes a user-supplied "reducer" callback function on each element produced by the iterator, passing in the return value from the calculation on the preceding element.Iterator.prototype.toArray()
. Creates anArray
with the populated yielded values.
Common ways for creating iterables are via the static method
Iterator.from()
and via the method values()
of
Array
, NodeList
and many other containers.
So the more memory efficient version of the
given example of transformation chaining will be:
arr.values().drop(10).take(10).filter(el => el < 10).map(el => el + 5).toArray()
.
One caveat is that it's a relatively new feature, the last mainstream browser that started to support this feature is Safari. It started to support from , so better to wait at least for several months.
Array at() method
Array.prototype.at()
is an alternative way for accessing the nth element. The cool thing is that it also supports negative indexing
where it will count from the last element.
For example [10,20,30].at(-1)
will return 30, [10,20,30].at(-2)
will return 20, etc.
This negative indexing makes so much easier to access the last elements. Before that you had to write this ugly
boilerplate code arr[arr.length - 1]
.
Promise.withResolvers()
Have you ever written such code in order to have the promise resolvers for the later use?
let resolve, reject;
const promise = new Promise((resolver, rejector) => {
resolve = resolver;
reject = rejector;
});
// use promise, resolve and reject later
// ......
Bulky, isn't it? Fortunately, this is now a thing of past since JS nowadays supports
Promise.withResolvers()
.
So you can write this instead:
const { promise, resolve, reject } = Promise.withResolvers();
// use promise, resolve and reject later
// ......
String.prototype.replace() / String.prototype.replaceAll() callback
This is an old thing, but many developers don't know that you can pass a callback instead of string for the
second parameter of String.prototype.replace()
or String.prototype.replaceAll()
. For
example:
let counter = 0;
console.log("NUMBER, NUMBER, NUMBER".replaceAll("NUMBER", (match) => match + "=" + (++counter))) // NUMBER=1, NUMBER=2, NUMBER=3
This is a very powerful thing, it allows to do many replacements with just one pass. Very efficient from both performance and memory standpoint. For more details please check the documentation.
Swapping variables
Another old thing. People oftentimes swap variables like this:
let a = 1, b = 2;
console.log(a, b); // 1, 2
const temp = a;
a = b;
b = temp;
console.log(a, b); // 2, 1
Do this instead:
let a = 1, b = 2;
console.log(a, b); // 1, 2
[a, b] = [b, a];
console.log(a, b); // 2, 1
structuredClone()
Browsers nowadays support
structuredClone()
API. This is a very convenient function for deeply copying most regular objects. However, people oftentimes use
JSON.stringify()
and JSON.parse()
for deeply copying objects without thinking whether
it's appropriate or not.
This is where things can go wrong when doing JSON.stringify()
/ JSON.parse()
:
-
JSON.stringify()
doesn't support some values, such asNaN
orundefined
. They can be skipped or converted intonull
. For some data types, such asbigint
, it will even throw exception. -
JSON.stringify()
cannot work with objects that contain cyclic references:const obj = {}; obj.selfReference = obj; console.log(JSON.stringify(obj)); // exception
- Although usually not as serious as the first 2, but I must say it's not efficient for larger objects. It's slow and wastes a lot of memory.
structuredClone()
should be preferred as much as possible. structuredClone()
also
automatically handles self referencing / cyclic structures.
const obj = {};
obj.selReference = obj;
const clonedObj = structuredClone(obj);
console.log(obj === clonedObj); // false, because it's a cloned object with a different memory address
console.log(clonedObj.selReference === clonedObj); // true, because it has the same structure as obj (isomorphic to obj, i.e. as a graph)
For more advanced information about object cloning and comparison, please read this.
Tagged templates
Most of us are familiar with template literals (``
), but many people are unaware of tagged
templates. Tagged templates allow you to parse template literals with a function. The first argument of a tag
function contains an array of string values. The remaining arguments are related to the expressions. Tagged
templates are useful when you want to do some automatic transformations to the interpolated values (or even the
whole string)
(values inside `${... here ...}`
).
For example we want to automatically escape html text when doing interpolation:
function escapeHtml(strings, ...arguments) {
const div = document.createElement("div");
let output = strings[0];
for (let i = 0; i < arguments.length; ++i) {
div.innerText = arguments[i];
output += div.innerHTML;
output += strings[i + 1];
}
return output;
}
console.log(escapeHtml`<br> ${'<br>'}`); // <br> <br>
WeakMap / WeakSet
Besides
Map
and
Set
JavaScript also supports
WeakMap
and
WeakSet
.
WeakMap
and WeakSet
are similar to Map
and Set
except that
they don't allow primitive values for their keys and they lack iterators. This is done because when you lose all
the
references pointing to a key the key and, possibly, the associated value must have the possibility to be freed
from the map / set and be garbage
collected.
const set = new WeakSet();
const map = new WeakMap();
{
const key1 = new Date();
const key2 = new Date();
console.log(set.has(key1)); // false
set.add(key1);
console.log(set.has(key1)); // true
console.log(map.get(key2)); // undefined
map.set(key2, 10);
console.log(map.get(key2)); // 10
}
// here we lost the references to key1 and key2, so the keys and values will be garbage collected later
Use WeakMap
or WeakSet
if you want to associate something with an object without any
side effects.
Set operations
Recently JavaScript has added support for boolean operations with
Set
objects.
Here is the list of boolean operations:
-
Set.prototype.difference()
. Returns a new set containing elements in this set but not in the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); console.log(set1.difference(set2)); // Set(2) {1, 2}
-
Set.prototype.intersection()
. Returns a new set containing elements in both this set and the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); console.log(set1.intersection(set2)); // Set(2) {3, 4}
-
Set.prototype.union()
. Returns a new set containing elements which are in either or both of this set and the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); console.log(set1.union(set2)); // Set(6) {1, 2, 3, 4, 5, 6}
-
Set.prototype.symmetricDifference()
. Returns a new set containing elements which are in either or both of this set and the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); console.log(set1.symmetricDifference(set2)); // Set(4) {1, 2, 5, 6}
-
Set.prototype.isDisjointFrom()
. Returns a boolean indicating if this set has no elements in common with the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); const set3 = new Set([5,6]); console.log(set1.isDisjointFrom(set2)); // false console.log(set1.isDisjointFrom(set3)); // true
-
Set.prototype.isSubsetOf()
. Returns a boolean indicating if all elements of this set are in the given set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); const set3 = new Set([5,6]); console.log(set1.isSubsetOf(set2)); // false console.log(set3.isSubsetOf(set2)); // true
-
Set.prototype.isSupersetOf()
. Returns a boolean indicating if all elements of the given set are in this set.const set1 = new Set([1,2,3,4]); const set2 = new Set([3,4,5,6]); const set3 = new Set([5,6]); console.log(set2.isSupersetOf(set1)); // false console.log(set2.isSupersetOf(set3)); // true