JavaScript is the native, natural, and productive way to create dynamic pages for all standard web browsers. Nowadays, JavaScript is everywhere — we can use it for building web services, desktop apps, mobile apps, CLI programs, and embedded systems. With this popularity, the ECMAScript standard introduces new language features for JavaScript to boost developers’ productivity further.
In this story, I’ll explain several modern JavaScript syntaxes you can practice in any JavaScript-based codebase to write futuristic, minimal, highly-readable, and clean source code!
As the name suggests, the spread syntax helps us to convert objects, iterable objects, such as arrays, sets, etc., into separate individual records. So, we can use this feature to construct new data structures based on existing data structures and pass iterable objects to functions as individual parameters. The spread syntax typically helps you to avoid verbose loop structures and make your source code more futuristic, readable, and self-explanatory. Let’s check several practical examples!
Before the ES6 spread syntax, we used the arguments
object to implement dynamic parameter counts in functions:
function sum() {
return Array
.from(arguments)
.reduce((acc, x) => acc + x);
}
console.log(sum(10, 20, 30)); // 60
The spread syntax can simplify the above code snippet further as follows:
function sum(...args) {
return args.reduce((acc, x) => acc + x);
}
console.log(sum(10, 20, 30)); // 60
In this example, the spread syntax even made the function definition more readable since the sum
function had zero parameters without the spread syntax before.
Modern developers frequently use the spread syntax to create/copy objects and arrays:
let numbers = [10, 20];
let letters = ['a', 'b'];
console.log([...numbers, ...letters]); // [ 10, 20, 'a', 'b' ]
let doc = {id: 100, title: 'new'};
console.log({...doc, copies: 2}); // { id: 100, title: 'new', copies: 2 }
The spread operator works with any object that implements the iterable protocol:
console.log([...new Set([2, 5])]); // [ 2, 5 ]
console.log([..."aeiou"]); // [ 'a', 'e', 'i', 'o', 'u' ]
console.log([...new Map([[0, 'a']])]); // [ [ 0, 'a' ] ]
Inline short-circuit evaluation techniques help us to avoid verbose if
conditions that typically set default values. The traditional if
condition first comes to our mind for any conditional programming requirement, but it doesn’t make every use of it effective and smart.
Look at the following code snippet:
function printDoc(doc) {
if(!doc.title)
doc.title = 'Untitled';
console.log(doc); // { id: 200, title: 'Untitled' }
// ...
}
printDoc({id: 200});
Here we can use an inline short-circuit ||
to simplify:
function printDoc(doc) {
doc.title = doc.title || 'Untitled';
console.log(doc); // { id: 200, title: 'Untitled' }
// ...
}
printDoc({id: 200});
The ||
operator sets the second value only if the first value evaluates to false
. Similarly, it’s possible to use the &&
boolean operator to set the second value when the first value evaluates to true
. JavaScript treats 0
and an empty string as false
in the boolean context, so we need to be careful while using short circuits in some situations.
For example, the following statement will never print 0
:
let doc = {id: 0};
console.log(doc.id || 200); // 200
This situation can make bugs in frontends that rely on RESTful APIs that often output 0
as a valid property value and output null
when a specific value isn’t set. The nullish coalescing operator sets the right-hand value only if the first value is null
or undefined
:
let doc = {id: 0};
console.log(doc.id ?? 200); // 0
Every programming language offers standard mathematical functions via its standard APIs. JavaScript has a pre-imported mathematical module in both the browser environment and Node.js. For exponentiation, we can use the Math.pow
inbuilt function with the base and exponent.
The ES7 exponentiation operator (**
) offers the same feature that the Math.pow
function provides, but with a better language grammar as follows:
console.log(2 ** 2); // 4
console.log(2 ** 10); // 1024
This is an excellent feature for full-stack developers who use Python backend services since Python had this syntax from its inception. Similar to Python, **
has higher precedence over *
(multiplication):
console.log(2 ** 5 * 2); // 64
console.log((2 ** 5) * 2); // 64
The exponentiation assignment also works the same as other assignment operators in JavaScript:
let n = 2;
n **= 2;
console.log(n); // 4
Nowadays, web developers tend to build web applications that fetch data from RESTful-based-like web services. These web services typically use JSON structures for communication, so programmers usually access nested data records by using the .
symbol. In some scenarios, they use nested data objects to transfer data between functions and modules. They even use callback functions inside complex objects.
The traditional .
symbol is a good technique, but it often leads to one of the most common (and critical) mistakes or bugs in JavaScript-based development: TypeErrors.
For example, the following code snippet triggers a bug when the assignee
property is not set (becomes undefined
):
let task1 = {
id: 101,
title: 'check attendance',
time: '10:50',
assignee: {
id: 50,
name: 'John'
}
};
let task2 = {
id: 102,
title: 'read agenda',
time: '11:00'
}
function showAssignee(task) {
let msg = `Assignee: ${task.assignee.name}`;
console.log(msg);
}
showAssignee(task1); // Assignee: John
showAssignee(task2); // TypeError!
The second showAssignee
function call throws an error since the assignee
field is undefined
. In the past, we solved these undefined property-related issues with short-circuits, ternary operators, and if
conditions:
// With ?:
function showAssignee(task) {
let name = task.assignee ? task.assignee.name : 'Nobody';
let msg = `Assignee: ${name}`;
console.log(msg);
}
// With && and || (short-circuits)
function showAssignee(task) {
let name = (task.assignee && task.assignee.name) || 'Nobody';
let msg = `Assignee: ${name}`;
console.log(msg);
}
// With if
function showAssignee(task) {
let name = 'Nobody';
if(task.assignee)
name = task.assignee.name;
let msg = `Assignee: ${name}`;
console.log(msg);
}
Now, the modern optional chaining syntax helps us to avoid nested property existence checks with ?.
:
let name = task.assignee?.name || 'Nobody';
This syntax becomes very helpful for minimizing code complexity since it returns undefined
when a property doesn’t exist instead of throwing a TypeError instance. So, you may write task.assignee?.address?.postalCode
instead of two undefined
property checks!
Let’s start this section with a question. Do you think that the following console.log
statements output the same number?
console.log(10);
console.log(010);
Some developers will say both outputs 10
assuming that the leading zero does not affect most programming languages’ number literals. In most languages (including JavaScript), octal number literals start with a leading zero. So, the second console.log
statement prints 8
(converted decimal value of octal10
) — not just10
.
This leading zero strategy is deprecated in the JavaScript strict mode, so we should use the standard approach: the 0o
prefix:
console.log(0o10);
JavaScript number literals also support binary and hexadecimal prefixes, as shown in the following example code snippet:
console.log(0b101); // 5
console.log(0xee); // 238
These inbuilt non-decimal literals are helpful when we work with Unix file permissions, color codes, and computer science-related applications. The great thing is that JavaScript automatically converts non-decimals when you print them on the console or process them with decimals:
console.log(0b101 + 5); // 5 + 5 -> 10
console.log(0b111 === 7) // 7 === 7 -> true
The ES6 standard came with the destructuring technique to help JavaScript developers to extract values from objects and arrays. The object destructuring technique typically supports us to unpack values from complex objects, as shown in the following example:
let doc = {
id: 110,
title: 'Untitled',
copies: 20,
paper: 'A4',
data: {
file: 'doc_110.bin',
format: 'pdf'
}
};
let { id, title, data: { file } } = doc;
console.log(id, title, file); // 110 Untitled doc_110.bin
The array destructuring syntax looks the same as the multiple assignment syntax in Python. But, in Python, the multiple assignment syntax is strict and must match the array length, so we often use the _
identifier to match the array length by unpacking unused records.
In JavaScript, destructuring-based multiple assignments are so flexible, and you don’t need to use unnamed identifiers for array records that you don’t need to unpack:
let A = [1, 2, 10];
let [a, b] = A;
console.log(a, b); // 1 2
Similar to Python’s *
list prefix, we can use the spread syntax to save the rest of the array records while unpacking:
let A = [1, 2, 10];
let [a, ...rest] = A;
console.log(a); // 1
console.log(rest); // [2, 10]