javascript

How to write *natural* code everybody will enjoy reading

The biggest reason we use languages like JavaScript and Python instead of Assembly is how much closer to natural language they are.

Or how much they CAN be!

Because sometimes we write code just for it to work without any care about demonstrating what we’re doing to other humans.

And then this backfires painfully down the line. Especially when one of those humans is your future self.

Parts of speech: back to basics

You know your code is natural when it resembles English as much as possible. Like an interesting, descriptive story.

It means you’ve intelligently made the entities and actions in the story to powerfully express the code flow from start to completion.

Nouns

What entities are we talking about?

  • Variables
  • Properties (getters & setters)
  • Classes & objects.
  • Modules

Every character has a name, so we describe them with expressive nouns and noun phrases.

Not this:

JavaScript
// ❌ do-examples.ts // ❌ Cryptic const f = 'Coding'; const l = 'Beauty'; // ❌ Verb const makeFullName = `${f} ${l}`; class Book { // ❌ Adjectival phrase createdAt: Date; }

But this:

JavaScript
// ✅ examples.ts // ✅ Readable const firstName = 'Coding'; const lastName = 'Beauty'; // ✅ Noun const fullName = `${firstName} ${lastName}`; class Book { // ✅ Noun phrase dateCreated: Date; }

Verbs

What are the actions in your codebase?

  • Functions
  • Object methods

An action means an entity is doing something; the naturally way to name them is with descriptive verbs and verb phrases.

Not this:

JavaScript
class Product { constructor(name, price, quantity) { this.name = name; this.price = price; this.quantity = quantity; } // ❌ Noun total() { return this.price * this.quantity; } } const product = new Product('Pineapple🍍', 5, 8); console.log(product.total()); // 40

But this:

JavaScript
class Product { constructor(name, price, quantity) { this.name = name; this.price = price; this.quantity = quantity; } // ✅ Verb getTotal() { return this.price * this.quantity; } } const product = new Product('Banana🍌', 7, 7); console.log(product.getTotal()); // 49

Methods are for doing something. Properties are for having something.

So better still:

JavaScript
class Product { constructor(name, price, quantity) { this.name = name; this.price = price; this.quantity = quantity; } get total() { return this.price * this.quantity; } } const product = new Product('Orange🍊', 7, 10); console.log(product.total); // 70

Adverbs

Remember an adverb tells you more about a noun or verb or another adverb.

In JavaScript this is any function that takes function and returns another: a higher-order function. They upgrade the function.

So instead of this:

JavaScript
// ❌ Verb function silence(fn) { try { return fn(); } catch (error) { return null; } } const getFileContents = (filePath) => silence(() => fs.readFileSync(filePath, 'utf8'));

It’ll be more natural to do this:

JavaScript
// ✅ Adverb function silently({ fn }) { // or "withSilence" try { return fn(); } catch (error) { return null; } } const getFileContents = (filePath) => silently({ fn: () => fs.readFileSync(filePath, 'utf8') });

It’s like saying, “Get the file contents silently“.

Natural inputs

Coding and computing are all about processing some input to produce output.

And in natural code the processing is action and the input + output are entities.

Let’s say we have a function that calculates a rectangle’s area and multiplies it by some amount.

Can you see the problem here?

JavaScript
const area = calculateArea(2, 5, 10); // 100

Which argument is the width and height? Which is the multiplier?

This code doesn’t read naturally; In English we always specify objects of an action.

How to fix this? Named arguments:

JavaScript
// Inputs are entities - nouns✅ const area = calculateArea({ multiplier: 2, height: 5, width: 10 }); function calculateArea({ multiplier, height, width }) { return multiplier * height * width; }

This is far easier to read; we instantly understand what inputs we’re dealing with.

Even when there’s just 1 argument I recommend using this.

Natural outputs

And we can be just as explicit in our outputs:

JavaScript
const { area } = calculateArea({ multiplier: 2, height: 5, width: 10, }); function calculateArea({ multiplier, height, width }) { return { area: multiplier * height * width }; }

Which also allows us easily upgrade the function later:

JavaScript
const { area, perimeter } = calculateArea({ multiplier: 2, height: 5, width: 10, }); console.log(area, perimeter); // 100 60 function calculateArea({ multiplier, height, width }) { return { area: multiplier * height * width, perimeter: (height + width) * 2 * multiplier, }; }

There’s no time for magic

Coding isn’t a mystery thriller! It’s a descriptive essay; be as descriptive as possible.

Not this cryptic mess:

JavaScript
function c(a) { return a / 13490190; } const b = c(40075); console.log(p); // 0.002970677210624906

What do all those numbers and variables mean in the bigger picture of the codebase? What do they tell us – the human?

Nothing. They tell us nothing. The entity & action names are either non-existent or of terrible quality.

It’s like telling your friend:

Yeah I went to this place and did this thing, then I did something to go to this other place and did something of 120!.

This is nonsense.

Natural code describes everything. Nice nouns for entities, great verbs for the actions.

JavaScript
const speedstersSpeedKmPerHr = 13490190; const earthCircumferenceKm = 40075; function calculateSpeedstersTime(distance) { return distance / speedstersSpeedKmPerHr; } const time = calculateSpeedstersTime(earthCircumferenceKm); console.log(time); // 0.002970677210624906 ~ 11s

Now you’ve said something.

Yeah I went to the restaurant and ate a chicken sandwich, then I drove to the gym and did bicep curls of 120 pounds!.

Create “useless” variables

Variables in natural code are no longer just for storing values here and there.

They’re also tools to explain what you’re doing:

That’s why instead of doing this:

JavaScript
if ( !user.isBanned && user.pricing === 'premium' && user.isSubscribedTo(channel) ) { console.log('Playing video...'); }

We’ll do this:

JavaScript
const canUserWatchVideo = !user.isBanned && user.pricing === 'premium' && user.isSubscribedTo(channel); if (canUserWatchVideo) { console.log('Playing video...'); }

We’re going to use the variable only once but it doesn’t matter. It’s not a functional variable but a cosmetic variable; a natural variable.

Final thoughts

Code is for your fellow humans too, not just compilers.

Can someone who doesn’t know how to code understand what’s going on in your code?

This is no doubt a powerful guiding question to keep your code as readable an natural as possible.

The 7 most transformative JavaScript features from ES10

JavaScript has come a long way in the past 10 years with brand new feature upgrades in each one.

Still remember when we created classes like this?

JavaScript
function Car(make, model) { this.make = make; this.model = model; } // And had to join strings like this Car.prototype.drive = function() { console.log("Vroom! This " + this.make + " " + this.model + " is driving!"); };

Yeah, a lot has changed!

Let’s take a look at the 7 most significant features that arrived in ES10 (2019); and see the ones you missed.

1. Modularization on the fly: Dynamic imports

The ES10 year was the awesome year when import could now act as function, like require(). An async function.

Keeping imports at the top-level was no longer a must; We could now easily resolve the module’s name at compile time.

Loading modules optionally and only when absolutely needed for high-flying performance…

JavaScript
async function asyncFunc() { if (condition) { const giganticModule = await import('./gigantic-module'); } }

Loading modules based on user or variable input…

JavaScript
import minimist from 'minimist'; const argv = minimist(process.argv.slice(2)); viewModule(argv.name); async function viewModule(name) { const module = await import(name); console.log(Object.keys(module)); }

It’s also great for using ES modules that no longer support require():

JavaScript
// ❌ require() of ES modules is not supported const chalk = require('chalk'); console.log(chalk.blue('Coding Beauty')); (async () => { // ✅ Runs successfully const chalk = (await import('chalk')).default; console.log(chalk.blue('Coding Beauty')); })();

2. Flattening the curve

flat() and flatMap() gave much cleaner ways to easily flatten multidimensional arrays.

Eradicating the need for painful array-looping flattening code:

JavaScript
const colorSwatches = [ 'cream🟡', ['scarlet🔴', 'cherry🔴'], ['blue🔷', ['sky blue🟦', 'navy blue🔵']], ]; // Default depth of 1 console.log(colorSwatches.flat()); // ['cream🟡', 'scarlet🔴', 'cherry🔴', 'blue🔷', // ['sky blue🟦', 'navy blue🔵']] console.log(colorSwatches.flat(2)); // ['cream🟡', 'scarlet🔴', 'cherry🔴', 'blue🔷', // 'sky blue🟦', 'navy blue🔵']

flatMap() is as good as calling map(), then flat(1):

JavaScript
const colorSwatches = [ 'cream🟡', ['scarlet🔴', 'cherry🔴'], ['blue🔷', ['sky blue🟦', 'navy blue🔵']], ]; // map to get only the emoji console.log( colorSwatches.flatMap((color) => Array.isArray(color) ? color : color.slice(-2) ) ); // [ '🟡', 'cherry🔴', 'blue🔷', [ 'sky blue🟦', 'navy blue🔵' ] ]

3. Transform arrays to objects

ES10 was also when Object.fromEntries() arrived on the JavaScript scene.

Quickly convert list of key-value pairs to equivalent key-value object:

JavaScript
const entries = [ ['name', 'The Flash⚡'], ['realName', 'Barry Allen'], ['lightningColor', 'yellow🟡'], ['suitColor', 'red🔴'], ]; console.log(Object.fromEntries(entries)); /** { name: 'The Flash⚡', realName: 'Barry Allen', lightningColor: 'yellow🟡', suitColor: 'red🔴' } */

4. Clean up your strings with precisions

trimStart() and trimEnd().

Before now everyone was using trim from NPM – Happily adding 3.35KB to the project…

Even now:

But slowly losing popularity to .trim().

Then Array trim() came along, then trimStart() and trimEnd().

JavaScript
const fruits = ' pineapple🍍 '; console.log(fruits.trimStart()); // 'pineapple🍍 ' console.log(fruits.trimEnd()); // ' pineapple🍍' console.log(fruits.trim()); // 'pineapple🍍'

5. Catching errors without the baggage

With the new optional catch binding, you now safely omit the catch block’s error argument when you had nothing to do with it:

JavaScript
// ❌ Before ES10 try { iMayExplodeAnyMomentNow(); } catch (err) { // Or else error } // ✅ try { iMayExplodeAnyMomentNow(); } catch {}

6. Sorting without surprise

Stable Array sort.

Previously when sorting an array there was absolutely no way we could guarantee the arrangement of the equal elements.

But in the post-ES10 JS code here, we are 100% sure that react always comes before vue always comes before angular.

JavaScript
const geniusPortfolio = [ { tool: 'javascript', years: 2000, }, { tool: 'react', years: 1000 }, { tool: 'vue', years: 1000 }, { tool: 'angular', years: 1000 }, { tool: 'assembly', years: 7000 }, ]; const sortedDesc = geniusPortfolio.sort((a, b) => { return b.years - a.years; }); const sortedAsc = geniusPortfolio.sort((a, b) => { return a.years - b.years; });

7. Go big or go home: BigInts

The name BigInt gives it purpose away: For loading up on unbelievably humongous integer values:

JavaScript
const bigInt = 240389470239846028947208942742089724204872042n; const bigInt2 = BigInt( '34028974029641089471947861048917649816048962' ); console.log(typeof bigInt); console.log(bigInt); console.log(typeof bigInt2); console.log(bigInt2); console.log(bigInt * bigInt2);

Because normal integers can’t:

JavaScript
// ✖️ Stored as double const normalInt = 240389470239846028947208942742089724204872042; const normalInt2 = 34028974029641089471947861048917649816048962; console.log(typeof normalInt); console.log(normalInt); console.log(typeof normalInt2); console.log(normalInt2); // ✖️ Precision lost console.log(normalInt * normalInt2);

Final thoughts

ES10 marked a significant leap forward for JavaScript with several features that have become essential for modern development.

Use them write cleaner code with greater conciseness, expressiveness, and clarity.

New array slice notation in JavaScript – array[start:stop:step]

With this new slice notation you’ll stop writing code like this:

JavaScript
const decisions = [ 'maybe', 'HELL YEAH!', 'No.', 'never', 'are you fr', 'uh, okay?', 'never', 'let me think about it', ]; const some = decisions.slice(1, 4); console.log(some); // [ 'HELL YEAH!', 'No.', 'are you fr' ]

And start writing code like this:

JavaScript
const decisions = [ 'maybe', 'HELL YEAH!', 'No.', 'never', 'are you fr', 'uh, okay?', 'never', 'let me think about it', ]; const some = decisions[1:4]; console.log(some); // [ 'HELL YEAH!', 'No.', 'are you fr' ]

Much shorter, readable and intuitive.

And we don’t even have to wait till it officially arrives — we can have it right now.

By extending the Array class:

JavaScript
Array.prototype.r = function (str) { const [start, end] = str.split(':').map(Number); return this.slice(start, end); }
JavaScript
const decisions = [ 'maybe', 'HELL YEAH!', 'No.', 'never', 'are you fr', 'uh, okay?', 'never', 'let me think about it', ]; const some = decisions.r('1:4'); console.log(some); // [ 'HELL YEAH!', 'No.', 'are you fr' ]

Slice it right to the end

Will it slice to the last item if we leave out the second number?

JavaScript
Array.prototype.r = function (str) { const [start, end] = str.split(':').map(Number); return this.slice(start, end); }; const yumFruits = [ 'apple🍎', 'banana🍌', 'orange🍊', 'strawberry🍓', 'mango🥭', ]; const some = yumFruits.r('1:'); console.log(some);

It doesn’t?

Because end is empty string and Number('') is 0, so we have arr.slice(n, 0) which is always an empty array.

Let’s upgrade r() with this new ability:

JavaScript
Array.prototype.r = function (str) { const [startStr, endStr] = str.split(':'); // 👇 Slice from start too const start = startStr === '' ? 0 : Number(startStr); // ✅ const end = endStr === '' ? this.length : Number(endStr); // ✅ return this.slice(start, end); }; const yumFruits = [ 'apple🍎', 'banana🍌', 'orange🍊', 'strawberry🍓', 'mango🥭', ]; console.log(yumFruits.r('1:')); console.log(yumFruits.r(':2')); console.log(yumFruits.r('1:3'));

Dealing with negativity

Can it handle negative indices?

JavaScript
const yumFruits = [ 'apple🍎', 'banana🍌', 'orange🍊', 'strawberry🍓', 'mango🥭', ]; console.log(yumFruits.r(':-2')); console.log(yumFruits.r('2:-1'));

It surely can:

The negative start or end is passed straight to slice() which already has built-in support for them.

Start-stop-step

We upgrade again to array[start:stop:step] – step for jumping across the array in constant intervals.

Like we see in Python (again):

Plain text
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // Index 2 to 7, every 2 elements print(arr[2:7:2])

This time slice() has no built-in stepping support, so we use a good old for loop to quickly leap through the array.

JavaScript
Array.prototype.r = function (str) { const [startStr, endStr, stepStr] = str.split(':'); const start = startStr === '' ? 0 : Number(startStr); // ⚒️ negative indexes const absStart = start < 0 ? this.length + start : start; const end = endStr === '' ? this.length : Number(endStr); const absEnd = end < 0 ? this.length + end : end; const step = stepStr === '' ? 1 : Number(stepStr); const result = []; for (let i = absStart; i < absEnd; i += step) { result.push(this[i]); } return result; }; const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; console.log(nums.r('2:7:2')); console.log(nums.r('8::1')); console.log(nums.r('-6::2')); console.log(nums.r('::3'));

Perfect:

Array reduce() does the exact same job elegant immutably.

I think there’s something about the function flow of data transformation that makes it elegant.

Readability

JavaScript
Array.prototype.r = function (str) { const [startStr, endStr, stepStr] = str.split(':'); const start = startStr === '' ? 0 : Number(startStr); const absStart = start < 0 ? this.length + start : start; const end = endStr === '' ? this.length : Number(endStr); const absEnd = end < 0 ? this.length + end : end; const step = stepStr === '' ? 1 : Number(stepStr); const result = this.reduce( ( acc, cur, index ) => index >= absStart && index < absEnd && (index - absStart) % step === 0 ? [...acc, cur] : acc, [] ); return result; };

Flip the script

What about stepping backwards?

Of course Python has it:

Plain text
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(arr[7:3:-1]) # [8, 7, 6, 5]

One thing you instantly notice here is start is greater than stop. This is a requirement for backward stepping.

Plain text
print(arr[3:7:-1]) # [] print(arr[7:3:1]) # []

Which makes sense: if you’re counting backwards you’re going from right to left so start should be more.

What do we do? Once again slice() does some of the heavy lifting for us…

We simply swap absStart and absEnd when step is negative

JavaScript
const [realStart, realEnd] = step > 0 ? [absStart, absEnd] : [absEnd, absStart]; // ❌ start > end (4:8), step: -1 -> (8:4) // ✅ end > start (7:3), step: -1 -> (3:7)

slice() returns an empty array when end > start:

JavaScript
const color = [ 'cream🟡', 'cobalt blue🔵', 'cherry🔴', 'celadon🟢', ]; console.log(color.slice(1, 3)); // [ 'cobalt blue🔵', 'cherry🔴' ] console.log(color.slice(3, 0)); // []

Now let’s combine everything together:

JavaScript
Array.prototype.r = function (str) { const [startStr, endStr, stepStr] = str.split(':'); const start = startStr === '' ? 0 : Number(startStr); const step = stepStr === '' ? 1 : Number(stepStr); const absStart = start < 0 ? this.length + start : start; // 👇 count to start for empty end when step is negative const end = endStr === '' ? (step > 0 ? this.length : 0) : Number(endStr); const absEnd = end < 0 ? this.length + end : end; const [realStart, realEnd] = step > 0 ? [absStart, absEnd] : [absEnd, absStart]; const slice = this.slice(realStart, realEnd); // 👈 if (slice.length === 0) return []; // 👈 const result = []; // 👇 for ( let i = absStart; step > 0 ? i < absEnd : i > absEnd; i += step ) { result.push(this[i]); } return result; }; const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; console.log(nums.r('2:7:2')); console.log(nums.r('-1:-7:-1')); console.log(nums.r('-7::-1')); console.log(nums.r('-5:9:-2')); console.log(nums.r('::3'));

We’ve come a long way! Remember how we started?

JavaScript
Array.prototype.r = function (str) { const [start, end] = str.split(':').map(Number); return this.slice(start, end); }

Yeah, and we didn’t even add any checks for wrong types and edge cases. And it goes without saying that I spent more than a few minutes debugging this…

And just imagine how it would be if we add multi-dimensional array support like in numpy:

JavaScript
import numpy as np sensor_data = np.array([ [10, 20, 30], [40, 50, 60], [70, 80, 90] ]) temperatures = sensor_data[:, 1] print(temperatures) # [20 50 80]

But with our new Array r() method, we’ve successfully brought Python’s cool array slicing notation to JavaScript.

7 little-known console methods in JavaScript

There’s more to console methods than log error and warn.

Did you know they’re actually up to 20?

And they do way more than logging text: colorful🎨 data visualization, debugging🔎, performance⚡ testing and so much more.

Check out these powerful 7:

1. table()

console.table(): Easily display object array as table: one row per object; one col per prop.

JavaScript
const fruits = [ { name: 'Mango', ripeness: 10, sweetness: '😋', }, { name: 'Banana', ripeness: 6, sweetness: '😏', }, { name: 'Watermelon', ripeness: 8, sweetness: '🤔', }, ]; console.table(fruits);

A bit different on Node:

Both clearly better than using console.log():

2. trace()

Where did we come from on the call stack? Let’s trace() our steps back!

Fantastic for debugging:

JavaScript
function coder() { goodCoder(); } function goodCoder() { greatCoder(); } function greatCoder() { greatSuperMegaGeniusCoder(); } function greatSuperMegaGeniusCoder() { console.log( "You only see the greatness now, but it wasn't always like this!" ); console.log( 'We\'ve been coding for generations -- let me show you...' ); console.trace(); } coder();

3. count()

console.count() logs the number of times that the current call to count() has been executed.

Too indirect and recursive a definition for me! You may need to see an example to really get it…

JavaScript
function shout(message) { console.count(); return message.toUpperCase() + '!!!'; } shout('hey'); shout('hi'); shout('hello');

console.count() has an internal counter starting at 0. After each call it increments the counter by 1 and logs it…

Much better.

Where did default come from? That’s the label for the counter. I’m guessing there’s an internal dictionary with a counter value for each label key.

console.count() has an internal counter starting at 0 for a new label. After each call it increments the counter by 1 and logs it…

We easily customize the label with the 1st argument to count().

JavaScript
function shout(message) { console.count(message); return message.toUpperCase() + '!!!'; } shout('hey'); shout('hi'); shout('hello'); shout('hi'); shout('hi'); shout('hello');

Now we have a different count for each message.

So countReset() obviously resets a label’s internal counter to 0.

JavaScript
function shout(message) { console.count(message); return message.toUpperCase() + '!!!'; } shout('hi'); shout('hello'); shout('hi'); shout('hi'); shout('hello'); console.log('resetting hi'); console.countReset('hi'); shout('hi');

4. clear()

CLS for JavaScript.

5. time() + timeLog() + timeEnd()

They work together to precisely measure how long a task takes.

57 microseconds, 11 seconds, 50 years? No problem.

  • time() – start the timer.
  • timeLog() – how far has it gone?
  • timeEnd() – stop the timer.

Let’s use them to compare the speed of all the famous JavaScript loop types.

JavaScript
console.time('for'); for (let i; i < 1000; i++) { for (let i = 0; i < arr.length; i++); } console.timeLog('for'); for (let i; i < 1000000; i++) { for (let i = 0; i < arr.length; i++); } console.timeEnd('for'); const arr1000 = [...Array(1000)]; const arr1000000 = [...Array(1000000)]; console.time('for of'); for (const item of arr1000); console.timeLog('for of'); for (const item of arr1000000); console.timeEnd('for of'); console.time('forEach'); arr1000.forEach(() => {}); console.timeLog('forEach'); arr1000000.forEach(() => {}); console.timeEnd('forEach');

for starts out slow but it destroys the other as the list grows…

6. group() + groupCollapsed() + groupEnd()

Another great combo for grouping a bunch of console messages together; visually with indentation and functionally with a UI expander.

  • group() – adds 1 further grouping level.
  • groupCollapsed() – like group() but group starts out collapsed.
  • groupEnd() – go back to previous grouping level.
JavaScript
console.log('What can we do?'); console.group('Abilities'); console.log('Run👟 - solid HIIT stuff'); console.groupCollapsed('Code💻'); console.log('JavaScript'); console.log('Python'); console.log('etc of course'); console.groupEnd(); console.log('Eat🍉 - not junk tho...');

Just indentation on Node – so groupCollapsed() has no use here.

7. dir()

dir() is a genius way to inspect an object in the console and see ALL it’s properties and methods.

Actually just like console.log()? 🤔 But console.dir() is specially designed for this particular purpose.

But watch what happens when you log() vs dir() HTML element objects:

log() shows it as a HTML tag hierarchy, but dir() shows it as an object with every single property it has and could ever dream of having.

Final thoughts

So they’re plenty of console methods apart from console.log(). Some of them spice things up in the console UI with better visualization; others are formidable tools for debugging and performance testing.

Never use magic numbers in your code: Do this instead

You can never guess what this does 👇

JavaScript
function c(a) { return a / 13490190; } const b = c(40075); console.log(p); // 0.002970677210624906

All we see is a calculation. We have no idea what those numbers and variables mean in the bigger picture of the codebase.

It’s a mystery. It’s magic to everyone apart from the developer who wrote the code.

We can’t possibly make any contributions to this code; we’re stuck.

But what if our dev had looked beyond code that merely works and also focused on communicating the full story of the code?

At the very least they would have given those variables far more descriptive names to explain the context:

JavaScript
function calculateSpeedstersTime(distance) { return distance / 13490190; } const time = calculateSpeedstersTime(40075); console.log(time); // 0.002970677210624906

This small change vastly improves the code’s readability. Now you have a general idea of what’s going on.

But there are still mysteries we must solve.

It’ll still take us a little bit to realize 13490190 is speed — but how much speed?. And we know 40075 is a distance, but why 40075 of all the numbers?

For maximum readability we replace these magic numbers with variables with explanatory names

JavaScript
const speedstersSpeedKmPerHr = 13490190; const earthCircumferenceKm = 40075; function calculateSpeedstersTime(distance) { return distance / speedstersSpeedKmPerHr; } const time = calculateSpeedstersTime(earthCircumferenceKm); console.log(time); // 0.002970677210624906 ~ 11s

Now you understand every single thing this code does at a glance.

Understanding at a glance is always the goal.

Even with readable code…

Anyone can understand this TS code here; but there’s a serious issue that could easily lead us to hours-long bug hunting.

Can you spot it?

JavaScript
class Person { state: string; name: string; greet() { console.log(`Hey I'm ${this.name}`); } eatFood() { if (this.state === 'tired') { this.state = 'fresh'; } } exercise() { if (this.state === 'fresh') { this.state = 'tired'; } } }

Problem: We’re lousily checking the state for equality and assigning with magic strings again and again.

Repeating ourselves, and allow a mere typo to break this code in the future. And the bug won’t always be easy to spot like it would be in this tiny code.

That’s why we have union types; similar to enums in TypeScript and other languages.

JavaScript
class Person { name: string; // ✅ Only 3 values, not infinite state: 'tired' | 'fresh' | 'sleepy'; greet() { console.log(`Hey I'm ${this.name}`); } eatFood() { if (this.state === 'tired') { this.state = 'fresh'; } } exercise() { // ✅ Typo: Error thrown if (this.state === 'fres') { this.state = 'tired'; } } }

Now 'tired' and 'fresh' are no longer random string literals. They’re now registered values of a pre-defined type.

And this is one of the powerful advantages TypeScript has over regular JS.

We even have this exact type of issue in a Dart codebase I’m part of. We’ve used the same magic strings like 10 times each.

But Dart only has enums and it’s not so easy to convert from magic strings to enums like we did here with union types. So we better refactor before it comes back to bite us!

The only thing that remains constant…

Keep using magic values and they keep spreading throughout the codebase across more and more files.

Replacing them is a chore.

JavaScript
// email.js export function sendEmail({ username }) { emailApi.send({ title: `Hi ${username ?? 'User'}`, role: 'user' // Union type value }); } // user-info.jsx export function UserInfo({ user }) { return ( <div> <div>Name: {user.name ?? 'User'}</div> <div>Email: {user.email}</div> </div> ); }

What happens when you want to change the default user name to something else but you’ve got the 'User' magic string in over 15 files?

Even Find and Replace will be tricky because they could be other strings with the same value but no relation.

We can fix this issue by creating a separate global config file containing app-wide values like this:

JavaScript
// app-values.js export const DEFAULT_USERNAME = 'User'; // ✅ Only one place to change // email.js import { DEFAULT_USERNAME } from './app-values'; export function sendEmail({ username, role }) { emailApi.send({ message: `Hi ${username ?? DEFAULT_USERNAME}`, // ✅ role: role ?? 'user', }); } // user-info.jsx import { DEFAULT_USERNAME } from './app-values'; export default function UserInfo({ user }) { return ( <div> <div>Name: {user.name ?? DEFAULT_USERNAME}</div> // ✅ <div>Email: {user.email}</div> </div> ); }

Final thoughts

Banish magic numbers and strings from your JavaScript code for clarity, maintainability, and efficiency.

By adopting these practices you pave the way for code that is not only functional but also self-documenting, collaborative, and resilient in the face of change.

3 ways to show line breaks in HTML without ever using br

Of course <br/> is what we all grew up with from our HTML beginnings, but there’s more.

Like… CSS white-space: pre-wrap:

HTML
<div id="box"> Coding is cognitively demanding, mentally stimulating, emotionally rewarding, beauty, unity power </div>
CSS
#box { background-color: #e0e0e0; width: 250px; font-family: Arial; white-space: pre-wrap; }

Without pre-wrap:

pre-wrap preserves line breaks and sequences of whitespace in the element’s text.

So the 4 spaces between the words in the first line are shown in the output along with the line break.

Even the space used for text indentation is also shown in the output, adding extra left padding to the container.

JS too

pre-wrap also acknowledges the \n character when set from JavaScript; with innerText or innerHTML:

JavaScript
const box = document.getElementById('box'); box.innerText = 'Coding is \n logic, art, growth, \n creation';

Without pre-wrap:

pre

pre works a lot like pre-wrap but no more auto wrapping:

For example:

HTML
<div id="box"> JavaScript at Coding Beauty HTML at Coding Beauty CSS at Coding Beauty </div>
CSS
#box { white-space: pre; background-color: #e0e0e0; width: 250px; font-family: Arial; }

If pre was pre-wrap in this example:

The behavior with pre is the same when you set the innerHTML or innerText property of the element to a string using JavaScript.

Newlines only

white-space:pre-line: Ignore extra spaces but show line breaks:

HTML
<div id="box"> Coding is growth unity power beauty </div>
CSS
#box { background-color: #e0e0e0; width: 250px; font-family: Arial; white-space: pre-line }

pre-line -> pre-wrap:

Like the previous two possible white-space values, pre-line works in the same way when you set the innerHTML or innerText property of the element to a string using JavaScript.

7 little-known but powerful array methods in JavaScript

There’s more to arrays than map(), filter(), find(), and push()

Check these out:

1. copyWithin()

Array copyWithin() copies a part of an array to another position in the same array and returns it without increasing its length.

JavaScript
const array = [1, 2, 3, 4, 5]; // copyWithin(target, start, end) // replace arr with start..end at target // a. target -> 3 (index) // b. start -> 1 (index) // c. end -> 3 (index) // start..end -> 2, 3 const result = array.copyWithin(3, 1, 3); console.log(result); // [1, 2, 3, 2, 3]

end parameter is optional:

JavaScript
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // "end" not specified so last array index used // target -> 0 (index) // start..end -> 6, 7, 8, 9, 10 const result = array.copyWithin(0, 5); // [6, 7, 8, 9, 10, 6, 7, 8, 9, 10] console.log(result);
JavaScript
const array = [1, 2, 3, 4, 5]; // Copy numbers 2, 3, 4, and 5 (cut off at index 4) const result = array.copyWithin(3, 1, 6); console.log(result); // [1, 2, 3, 2, 3]

2. at() and with()

at() came first and with() came a year after that in 2023.

They are the functional and immutable versions of single-element array modification and access.

JavaScript
const colors = ['pink', 'purple', 'red', 'yellow']; console.log(colors.at(1)); // purple console.log(colors.with(1, 'blue')); // ['pink', 'blue', 'red', 'yellow'] // Original not modified console.log(colors); // ['pink', 'purple', 'red', 'yellow']

The cool thing about these new methods is how they let you get and change element values with negative indexing.

3. Array reduceRight() method

Works like reduce() but the callback goes from right to left instead of left to right:

JavaScript
const letters = ['b', 'e', 'a', 'u', 't', 'y']; const word = letters.reduce((word, letter) => word + letter, ''); console.log(word); // beauty // Reducer iterations // 1. ('', 'y') => '' + 'y' = 'y' // 2. ('y', 't') => 'y' + 't' = 'yt'; // 3. ('yt', 'u') => 'ytu'; // ... // n. ('ytuae', 'b') => 'ytuaeb'; const wordReversed = letters.reduceRight((word, letter) => word + letter, ''); console.log(wordReversed); // ytuaeb

Here’s another great scenario for reduceRight():

JavaScript
const thresholds = [ { color: 'blue', threshold: 0.7 }, { color: 'orange', threshold: 0.5 }, { color: 'red', threshold: 0.2 }, ]; const value = 0.9; const threshold = thresholds.reduceRight((color, threshold) => threshold.threshold > value ? threshold.color : color ); console.log(threshold.color); // red

4. Array findLast() method

New in ES13: find array item starting from last element.

Great for cases where where searching from end position produces better performance than with find()

Example:

JavaScript
const memories = [ // 10 years of memories... { date: '2020-02-05', description: 'My first love' }, // ... { date: '2022-03-09', description: 'Our first baby' }, // ... { date: '2024-01-25', description: 'Our new house' }, ]; const currentYear = new Date().getFullYear(); const query = 'unique'; const milestonesThisYear = events.find( (event) => new Date(event.date).getFullYear() === currentYear && event.description.includes(query) );

This works but as our target object is closer to the tail of the array, findLast() should run faster:

JavaScript
const memories = [ // 10 years of memories... { date: '2020-02-05', description: 'My first love' }, // ... { date: '2022-03-09', description: 'Our first baby' }, // ... { date: '2024-01-25', description: 'Our new house' }, ]; const currentYear = new Date().getFullYear(); const query = 'unique'; const milestonesThisYear = events.findLast( (event) => new Date(event.date).getFullYear() === currentYear && event.description.includes(query) );

Another use case for findLast() is when we have to specifically search the array from the end to get the correct element.

For example, if we want to find the last even number in a list of numbers, find() would produce a totally wrong result:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // gives 14, instead of 10 const lastEven = nums.find((value) => value % 2 === 0); console.log(lastEven); // 14

But findLast() will start the search from the end and give us the correct item:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; const lastEven = nums.findLast((num) => num % 2 === 0); console.log(lastEven); // 10

5. toSorted(), toReversed(), toSpliced()

ES2023 came fully packed with immutable versions of sort(), reverse(), and splice().

Okay maybe splice() isn’t used as much as the others, but they all mutate the array in place.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.reverse(); console.log(reversed); // [2, 4, 3, 1, 5] (same array) console.log(original); // [2, 4, 3, 1, 5] (mutated) const sorted = original.sort(); console.log(sorted); // [1, 2, 3, 4, 5] (same array) console.log(original); // [1, 2, 3, 4, 5] (mutated) const deleted = original.splice(1, 2, 7, 10); console.log(deleted); // [2, 3] (deleted elements) console.log(original); // [1, 7, 10, 4, 5] (mutated)

Immutability gives us predictable and safer code; debugging is much easier as we’re certain variables never change their value.

Arguments are exactly the same, with splice() and toSpliced() having to differ in their return value.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.toReversed(); console.log(reversed); // [2, 4, 3, 1, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const sorted = original.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const spliced = original.toSpliced(1, 2, 7, 10); console.log(spliced); // [1, 7, 10, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged)

6. Array lastIndexOf() method

The lastIndexOf() method returns the last index where a particular element can be found in an array.

JavaScript
const colors = ['a', 'e', 'a', 'f', 'a', 'b']; const index = colors.lastIndexOf('a'); console.log(index); // 4

We can pass a second argument to lastIndexOf() to specify an index in the array where it should stop searching for the string after that index:

JavaScript
const colors = ['a', 'e', 'a', 'f', 'a', 'b']; // Get last index of 'a' before index 3 const index1 = colors.lastIndexOf('a', 3); console.log(index1); // 2 const index2 = colors.lastIndexOf('a', 0); console.log(index2); // 0 const index3 = colors.lastIndexOf('f', 2); console.log(index3); // -1

7. Array flatMap() method

The flatMap() method transforms an array using a given callback function and then flattens the transformed result by one level:

JavaScript
const arr = [1, 2, 3, 4]; const withDoubles = arr.flatMap((num) => [num, num * 2]); console.log(withDoubles); // [1, 2, 2, 4, 3, 6, 4, 8]

Calling flatMap() on the array does the same thing as calling map() followed by a flat() of depth 1, but it`s a bit more efficient than calling these two methods separately.

JavaScript
const arr = [1, 2, 3, 4]; // flat() uses a depth of 1 by default const withDoubles = arr.map((num) => [num, num * 2]).flat(); console.log(withDoubles); // [1, 2, 2, 4, 3, 6, 4, 8]

Final thoughts

They are not that well-known (yet) but they have their unique uses and quite powerful.

Master JavaScript Mutation Observer: amazing real-world use cases

JavaScript Mutation Observer.

An underrated API for watching for changes in the DOM: “child added”, “attribute changed”, and more.

JavaScript
// Node/element to be observed for mutations const targetNode = document.getElementById('my-el'); // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === 'childList') { console.log('A child node has been added or removed.'); } else if (mutation.type === 'attributes') { console.log( `The ${mutation.attributeName} attribute was modified.` ); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(targetNode, config); // Later, you can stop observing observer.disconnect();

I see a bunch of people online curious to know the point of this API in the real-world.

In this post I’ll show you how I used it to easily shut-up this annoying script carrying out unwanted activities on the page.

And you’ll see how sleek and polish the UI elements became after this.

The 3rd party script

It came from Mailchimp – a service for managing email lists.

Mailchimp provides embedded forms to easily collect sign-ups to your list.

These forms are HTML code embedded in your webpage without any additional configuration.

But being this ready-to-use nature comes at a cost; deeply embedded CSS stubbornly resisting any attempt to customize the form’s look and feel.

I mean I know I definitely spent WAY more than I should have doing this, having to throw CSS !important all over the place and all…

Stubbornly rigid JS

On the JS side I had the remote Mailchimp script showing these pre-defined error/success messages after the request to the Mailchimp API.

Sure this was an decent success message but there was simply no built-in option to customize it. And we couldn’t indicate success in other ways like a color or icon change.

Mutation Observer to the rescue…

Waiting for the precise moment the success text came in and instantly swapping out the copy for whatever I wanted and doing anything else.

And just like that the script was blocked from directly affecting the UI.

We basically turned it into an Event Emitter service; an observable.

This let us easily abstract everything into a React component and add various event listeners (like onSuccess and onError):

To create the polished form we saw earlier:

This is the power of the JavaScript Mutation Observer API.

7 new JavaScript Set methods: union(), intersection() + 5 more

Let’s be honest: you probably don’t care about Sets! At least until now…

They’ve been here since ES6 but they’re usually relegated to making sure a list has no duplicates.

JavaScript
const array = [1, 4, 3, 2, 3, 1]; const noDuplicates = [...new Set(array)]; console.log(noDuplicates); // [1, 4, 3, 2]

With these 7 upcoming built-in Set methods, we could find ourselves using them a LOT more often.

1. union()

The new Set union() method gives us all the unique items in both sets.

JavaScript
const creation = new Set(['coding', 'writing', 'painting']); const joy = new Set(['crying', 'laughing', 'coding']); console.log(creation.union(joy)); // Set { 'coding', 'crying', 'writing', 'laughing', 'painting' }

And since it’s immutable and returns a copy, you can chain it indefinitely:

JavaScript
const odd = new Set([21, 23, 25]); const even = new Set([20, 22, 24]); const prime = new Set([23, 29]); console.log(odd.union(even).union(prime)); // Set(7) { 21, 23, 25, 20, 22, 24, 29 }

2. intersection()

What elements are in both sets?

JavaScript
const mobile = new Set(['javascript', 'java', 'swift', 'dart']); const backend = new Set(['php', 'python', 'javascript', 'java']); const frontend = new Set(['javascript', 'dart']); console.log(mobile.intersection(backend)); // Set { javascript, java } console.log(mobile.intersection(backend).intersection(frontend)); // Set { javascript }

3. difference()

difference() does A – B to return all the elements in A that are not in B:

JavaScript
const joy = new Set(['crying', 'laughing', 'coding']); const pain = new Set(['crying', 'screaming', 'coding']); console.log(joy.difference(pain)); // Set { 'laughing' }

4. symmetricDifference()

As symmetric implies, this method gets the set difference both ways. That’s (A – B) U (B – A).

All the items in 1 and only 1 of the sets:

JavaScript
const joy = new Set(['crying', 'laughing', 'coding']); const pain = new Set(['crying', 'screaming', 'coding']); console.log(joy.symmetricDifference(pain)); // Set { 'laughing', 'screaming' }

5. isSubsetOf()

Purpose is clear: check if all elements of a set are in another set.

JavaScript
const colors = new Set(['indigo', 'teal', 'cyan', 'violet']); const purpleish = new Set(['indigo', 'violet']); const secondary = new Set(['orange', 'green', 'violet']); console.log(purpleish.isSubsetOf(colors)); // true console.log(secondary.isSubsetOf(colors)); // false console.log(colors.isSubsetOf(colors)); // true

6. isSupersetOf()

Check if one set contains all the elements in another set: As good as swapping the two sets in isSubsetOf():

JavaScript
const colors = new Set(['salmon', 'cyan', 'yellow', 'aqua']); const blueish = new Set(['cyan', 'aqua']); const primary = new Set(['red', 'yellow', 'blue']); console.log(colors.isSupersetOf(blueish)); // true console.log(colors.isSupersetOf(primary)); // false console.log(colors.isSupersetOf(colors)); // true

7. isDisjointFrom()

isDisjointFrom: Do these sets share zero common elements?

JavaScript
const ai = new Set(['python', 'c++']); const mobile = new Set(['java', 'js', 'dart', 'kotlin']); const frontend = new Set(['js', 'dart']); console.log(ai.isDisjointFrom(mobile)); // true console.log(mobile.isDisjointFrom(frontend)); // false

Use them now

With core-js polyfills:

Otherwise you get blasted with errors from TypeScript & Node.js — they’re not yet in the official JavaScript standard.

Wrap up

So these are our 7 new Set methods — no more need for 3rd parties like _.intersection() (Lodash!)

JavaScript
const unique = new Set(['salmon', 'cyan', 'cherry', 'aqua']); const blueish = new Set(['cyan', 'aqua', 'blue']); const primary = new Set(['red', 'green', 'blue']); console.log(unique.union(blueish)); // Set { 'salmon', 'cyan', 'cherry', 'aqua', 'blue' } console.log(unique.intersection(blueish)); // Set { 'cyan', 'aqua' } console.log(unique.difference(blueish)); // Set { 'salmon', 'cherry' } console.log(unique.symmetricDifference(blueish)); // Set { 'salmon', 'cherry', 'blue' } console.log(primary.isSubsetOf(unique)); // false console.log(new Set(['red', 'green']).isSubsetOf(primary)); // true console.log(unique.isSupersetOf(new Set(['salmon', 'aqua']))); // true console.log(unique.isSupersetOf(blueish)); // false console.log(unique.isDisjointFrom(primary)); // true console.log(unique.isDisjointFrom(blueish)); // false

Loop through HTML child elements/nodes in JavaScript

In JavaScript, working with the Document Object Model (DOM) often involves iterating through child elements of a parent element. This technique is essential for tasks such as:

  • Manipulating elements based on their content or attributes
  • Dynamically adding or removing elements
  • Handling events for multiple elements

JavaScript offers several methods to achieve this, each with its own advantages and considerations.

Methods for looping

1. Use children property

  • Access the children property of the parent element to obtain a live NodeList of its direct child elements.
  • Iterate through the NodeList using a for loop or other methods:
JavaScript
const parent = document.getElementById("myParent"); const children = parent.children; for (let i = 0; i < children.length; i++) { const child = children[i]; // Perform actions on the child element console.log(child.textContent); }

2. Use for..of loop

Directly iterate over the NodeList using the for...of loop:

JavaScript
const parent = document.getElementById("myParent"); for (const child of parent.children) { // Perform actions on the child element console.log(child.tagName); }

3. Use Array.from() method

Convert the NodeList into an array using Array.from(), allowing the use of array methods like forEach():

JavaScript
const parent = document.getElementById("myParent"); const childrenArray = Array.from(parent.children); childrenArray.forEach(child => { // Perform actions on the child element child.style.color = "red"; });

Key considerations

  • Live NodeList: The children property returns a live NodeList, meaning changes to the DOM are reflected in the NodeList.
  • Text Nodes: The children property includes text nodes, while childNodes includes all types of nodes (elements, text, comments, etc.). Choose the appropriate property based on your needs.
  • Performance: For large DOM trees, using Array.from() might have a slight performance overhead due to array creation.

Choosing the right method

  • For simple iterations, the for...of loop or the children property with a for loop are often sufficient.
  • If you need to use array methods or want to create a static copy of the child elements, use Array.from().
  • Consider performance implications if dealing with large DOM structures.

By understanding these methods and their nuances, you’ll be able to effectively loop through child elements in JavaScript for various DOM manipulation tasks.