Free Online Code Editor
A hands-on tour of the modern JavaScript features that make everyday code shorter and safer, from destructuring and ?. to structuredClone and toSorted.
JavaScript has changed a lot since 2015. Many habits from old tutorials, like var, string concatenation, and manual null checks, have better replacements. Here are the features that pay off the most, each with a short example you can run.
Use const by default. Switch to let only when the value has to change. Both are block-scoped, which avoids a whole family of bugs var caused.
const limit = 3;
let total = 0;
for (let i = 0; i < limit; i++) {
total += i;
}
console.log(total);3const doesn't make objects frozen. It only stops you from reassigning the name. You can still change what's inside an object or array.
Backticks let you embed values and write multi-line strings without +.
const name = "Ana";
const items = 3;
console.log(`Hi ${name}, you have ${items} item${items === 1 ? "" : "s"} in your cart.`);Hi Ana, you have 3 items in your cart.Pull values out of objects and arrays in one line.
const user = { name: "Ben", role: "admin", address: { city: "Toronto" } };
const { name, role } = user;
const { address: { city } } = user;
console.log(name, role, city);
const [first, second = "none"] = ["a"];
console.log(first, second);Ben admin Toronto
a noneIt's especially nice in function parameters, where you can name the options you care about and give them defaults:
function createButton({ label, size = "medium", disabled = false }) {
return `${label} (${size}${disabled ? ", disabled" : ""})`;
}
console.log(createButton({ label: "Save" }));
console.log(createButton({ label: "Delete", disabled: true }));Save (medium)
Delete (medium, disabled)Three dots do two related jobs. In a call or literal they spread things out. In a parameter list they gather things up.
const defaults = { theme: "light", fontSize: 14 };
const custom = { fontSize: 18 };
const settings = { ...defaults, ...custom };
console.log(settings);
const nums = [1, 2, 3];
const more = [...nums, 4, 5];
console.log(more);
function sum(...values) {
return values.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));{ theme: 'light', fontSize: 18 }
[ 1, 2, 3, 4, 5 ]
10Spreading makes a shallow copy. For a deep copy, use structuredClone, covered below.
These two replace a lot of defensive if statements.
const profile = { name: "Cy", social: null };
console.log(profile.social?.twitter);
console.log(profile.address?.city ?? "Unknown city");
console.log(profile.getAge?.() ?? "no getAge method");undefined
Unknown city
no getAge method?. stops and returns undefined if anything along the path is null or undefined. ?? supplies a fallback only for null or undefined.
That's different from ||, which replaces any falsy value. This matters with numbers and empty strings:
const volume = 0;
console.log(volume || 50);
console.log(volume ?? 50);50
0With ||, a volume of zero becomes 50, which is almost never what you want.
const people = [
{ name: "Ana", age: 31 },
{ name: "Ben", age: 17 },
{ name: "Cy", age: 45 },
];
const adults = people.filter((p) => p.age >= 18);
const names = adults.map((p) => p.name);
const total = adults.reduce((sum, p) => sum + p.age, 0);
console.log(names, total);
console.log(people.find((p) => p.age > 40));
console.log(people.some((p) => p.age < 18));
console.log(people.every((p) => p.age < 18));[ 'Ana', 'Cy' ] 76
{ name: 'Cy', age: 45 }
true
falseNewer additions are worth knowing too:
const list = [5, 1, 4];
console.log(list.at(-1));
console.log(list.toSorted((a, b) => a - b));
console.log(list);
console.log([[1, 2], [3, [4]]].flat(2));4
[ 1, 4, 5 ]
[ 5, 1, 4 ]
[ 1, 2, 3, 4 ]at(-1) gets the last item. toSorted returns a sorted copy and leaves the original alone, unlike sort, which changes the array in place.
const prices = { apple: 1, pear: 2 };
const doubled = Object.fromEntries(
Object.entries(prices).map(([item, price]) => [item, price * 2])
);
console.log(doubled);
const original = { tags: ["a"], meta: { views: 1 } };
const copy = structuredClone(original);
copy.meta.views = 99;
console.log(original.meta.views);{ apple: 2, pear: 4 }
1structuredClone makes a true deep copy, so changing the copy never affects the original.
Arrow functions are shorter, and they don't create their own this. That's great for callbacks and a trap for object methods.
const timer = {
label: "Timer A",
describeAll() {
return ["x", "y"].map((item) => `${this.label}: ${item}`);
},
};
console.log(timer.describeAll());[ 'Timer A: x', 'Timer A: y' ]Use arrow functions for callbacks. Use regular method syntax (sayHi() { ... }) for methods on objects and classes.
const first, let when needed, never var.?. and ?? handle missing data safely. Use ??, not ||, when 0 or "" are valid values.toSorted and structuredClone when you don't want to change the original.Reading helps, but typing the code is what makes it stick. Open the editor, paste any example from this article, and change it until it breaks.
Open the JavaScript editorUnderstand JavaScript promises and async/await step by step, including running tasks in parallel with Promise.all and the mistakes that cause silent bugs.
Learn how to find elements, change text and classes, create new nodes, and handle clicks with event delegation, plus how to avoid common XSS mistakes.
Learn how to catch, throw, and design errors in JavaScript, including custom error classes, finally, and the async mistakes that let failures slip through.