ES2020 Optional Chaining and Dynamic Imports: Reducing Code and Boosting Performance
This article explains the ES2020 optional chaining (?.) and dynamic import() features, showing their syntax, practical code examples, compatibility notes, and how they simplify JavaScript code while improving web application performance.
Since the release of ES6 in 2015, JavaScript has undergone several upgrades, and the ES2020 specification introduced two game‑changing features: optional chaining (?.) and dynamic imports. Using these features can dramatically reduce the amount of boilerplate code in existing projects.
Optional Chaining
The optional chaining operator ?. works like the dot operator but stops evaluation when the left‑hand side is undefined or null , returning undefined instead of throwing an error. This allows safe access to deeply nested properties without explicit existence checks.
Syntax
Official documentation defines four forms:
obj.val?.prop
obj.val?.[expr]
obj.arr?.[index]
obj.func?.(args)Usage Examples
Property access
let book = {
name: "Harry Potter 1",
price: { value: 50, currency: "EUR" },
ISBN: "978-7-7058-9615-2"
};
console.log(book.price.value); // 50
console.log(book.weight); // undefined
// console.log(book.weight.value); // throws
console.log(book?.weight?.value); // undefinedExpression access
let userInputProperty = document.getElementById('inputProperty').value;
let userInputNestedProperty = document.getElementById('inputPropertyNested').value;
console.log(book[userInputProperty][userInputNestedProperty]); // may throw
console.log(book?.[userInputProperty]?.[userInputNestedProperty]); // safe, returns undefined if missingArray element access
let books = [{ name: "Harry Potter 1", price: { value: 50, currency: "EUR" }, ISBN: "978-7-7058-9615-2" },
{ name: "Harry Potter 2", price: { value: 60, currency: "EUR" }, ISBN: "978-3-2560-1878-4" }];
console.log(books[1].price.value); // 60
// console.log(books[2].price.value); // throws
console.log(books?.[2]?.price?.value); // undefinedFunction call
let bookModule = {
getBooks: function() {
console.log("Books returned");
return true;
}
};
bookModule.getBooks(); // "Books returned"
// bookModule.getBook(); // throws
bookModule?.getBook?.(); // undefined, no errorImportant Note
Optional chaining works only on the right‑hand side of an assignment; using it on the left‑hand side results in a syntax error.
let book = { name: "Harry Potter 1", price: { value: 50, currency: "EUR" } };
book?.weight?.value = 650; // SyntaxError
book.weight = { value: 650 }; // validBefore vs. After
Prior to optional chaining, developers needed explicit checks for each nesting level, leading to verbose code. With ?. , the same logic collapses into a single, readable expression.
// Verbose checks
if (book && book.weight && book.weight.version2 && book.weight.version2.value) {
console.log(book.weight.version2.value);
}
// Using optional chaining
console.log(book?.weight?.version2?.value); // 690
console.log(book?.weight?.version3?.value); // undefinedBrowser Compatibility
According to CanIUse, optional chaining is supported by all modern browsers, with polyfills available for older versions.
Dynamic Imports
Dynamic import() enables native, on‑demand loading of JavaScript modules at runtime, allowing code to be fetched only when needed. This reduces initial bundle size and improves page load performance.
Why Dynamic Imports?
Static import statements must appear at the top level, accept only string literals, and cannot be conditional. Dynamic imports remove these constraints, supporting variable module specifiers and conditional loading.
Static vs. Dynamic Import Comparison
Static import
import { exportAsCSV } from './export-as-csv.js';
import { exportAsXML } from './export-as-xml.js';
const dataBlock = getData();
exportCSVButton.addEventListener('click', () => exportAsCSV(dataBlock));
exportXMLButton.addEventListener('click', () => exportAsXML(dataBlock));Both modules are loaded regardless of whether the user clicks the corresponding button, increasing the initial download size.
Dynamic import
const dataBlock = getData();
exportCSVButton.addEventListener('click', () => {
import('./export-as-csv.js')
.then(module => { module.exportAsCSV(dataBlock); })
.catch(err => { /* handle load error */ });
});
exportXMLButton.addEventListener('click', () => {
import('./export-as-xml.js')
.then(module => { module.exportAsXML(dataBlock); })
.catch(err => { /* handle load error */ });
});Modules are fetched only when the user triggers the associated action, reducing the amount of JavaScript transferred on initial page load.
Browser Compatibility
CanIUse shows that dynamic import() is supported in all major modern browsers, with fallbacks available for older environments.
Both optional chaining and dynamic imports are powerful ES2020 features that let developers write cleaner, more maintainable code without sacrificing performance.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.