Tari Ibaba

Tari Ibaba is a software developer with years of experience building websites and apps. He has written extensively on a wide range of programming topics and has created dozens of apps and open-source libraries.

The 5 most transformative JavaScript features from ES14

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 5 most significant features that arrived in ES14 (2023); and see the ones you missed.

1. toSorted()

Sweet syntactic sugar.

ES14’s toSorted() method made it easier to sort an array and return a copy without mutation.

Instead of this:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const sorted = clone.sort(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7] // ❌❌ Mutated console.log(nums); // [1, 2, 3, 4, 5, 6, 7]

We now got to do this ✅:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; // ✅ toSorted() prevents mutation const sorted = nums.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7] console.log(nums); // [5, 2, 6, 3, 1, 7, 4]

toSorted() takes a callback for controlling sorting behavior – ascending or descending, alphabetical or numeric. Just like sort().

2. Array find from last

Searching from the first item isn’t always ideal:

JavaScript
const tasks = [ { date: '2017-03-05', name: '👟run a 5k' }, { date: '2017-03-04', name: '🏋️lift 100kg' }, { date: '2017-03-04', name: '🎶write a song' }, // 10 million records... { date: '2024-04-24', name: '🛏️finally sleep on time' }, { date: '2024-04-24', name: '📝1h writing with no breaks' }, ]; const found = tasks.find((item) => item.date === '2024-03-25'); const foundIndex = tasks.findIndex((item) => item.date === '2024-03-25'); console.log(found); // { value: '2024-03-25', name: 'do 1000 pushups' } console.log(foundIndex); // 9,874,910

You can easily see that it’ll be much faster for me to search our gigantic list from the end instead of start.

JavaScript
const tasks = [ { date: '2017-03-05', name: 'run a 5k' }, { date: '2017-03-04', name: 'lift 100kg' }, { date: '2017-03-04', name: 'write a song' }, // 10 million records... { date: '2024-04-24', name: 'finally sleep on time' }, { date: '2024-04-24', name: '1h writing with no breaks' }, ]; // ✅ Much faster const found = tasks.findLast((item) => item.date === '2024-03-25'); const foundIndex = tasks.findLastIndex((item) => item.date === '2024-03-25'); console.log(found); // { value: '2024-03-25', name: 'do 1000 pushups' } console.log(foundIndex); // 9,874,910

And they’re also times you MUST search from the end for your program work.

Like we want to find the last even number in a list of numbers, find and findIndex will be incredibly off.

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

And calling reverse() won’t work either, even as slow as it would be:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // ❌ Copying the entire array with the spread syntax before // calling reverse() const reversed = [...nums].reverse(); // correctly gives 10 const lastEven = reversed.find((value) => value % 2 === 0); // ❌ gives 1, instead of 4 const reversedIndex = reversed.findIndex((value) => value % 2 === 0); // Need to re-calculate to get original index const lastEvenIndex = reversed.length - 1 - reversedIndex; console.log(lastEven); // 10 console.log(reversedIndex); // 1 console.log(lastEvenIndex); // 4

So in cases like where the findLast() and findLastIndex() methods come in handy.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // ✅ Search from end const lastEven = nums.findLast((num) => num % 2 === 0); // ✅ Maintain proper indexes const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0); console.log(lastEven); // 10 console.log(lastEvenIndex); // 4

This code is shorter and more readable. Most importantly, it produces the correct result.

3. toReversed()

Another new Array method to promote immutability and functional programming.

Before – with reverse() ❌:

JavaScript
const arr = [5, 4, 3, 2, 1]; const reversed = arr.reverse(); console.log(reversed); // [1, 2, 3, 4, 5] // ❌ Original modified console.log(arr); // [1, 2, 3, 4, 5]

Now – with toReversed() ✅:

JavaScript
const arr = [5, 4, 3, 2, 1]; const reversed = arr.toReversed(); console.log(reversed); // [1, 2, 3, 4, 5] // ✅ No modification console.log(arr); // [5, 4, 3, 2, 1]

I find these immutable methods awesome for constantly chaining methods over and over without worrying about the original variables:

JavaScript
// ✅ Results are independent of each other const nums = [5, 2, 6, 3, 1, 7, 4]; const result = nums .toSorted() .toReversed() .map((n) => n * 2) .join(); console.log(result); // 14,12,10,8,6,4,2 const result2 = nums .map((n) => 1 / n) .toSorted() .map((n) => n.toFixed(2)) .toReversed(); console.log(result2); // [ '1.00', '0.50', '0.33', '0.25', // '0.20', '0.17', '0.14' ]

4. toSpliced()

Lovers of functional programming will no doubt be pleased with all these new Array methods.

This is the immutable counterpart of .splice():

JavaScript
const colors = ['red🔴', 'purple🟣', 'orange🟠', 'yellow🟡']; // Remove 2 items from index 1 and replace with 2 new items const spliced = colors.toSpliced(1, 2, 'blue🔵', 'green🟢'); console.log(spliced); // [ 'red🔴', 'blue🔵', 'green🟢', 'yellow🟡' ] // Original not modified console.log(colors); // ['red🔴', 'purple🟣', 'orange🟠', 'yellow🟡'];

5. Array with() method

with() is our way of quickly change an array element with no mutation whatsoever.

Instead of this usual way:

JavaScript
const arr = [5, 4, 7, 2, 1] // Mutates array to change element arr[2] = 3; console.log(arr); // [5, 4, 3, 2, 1]

ES14 now let us do this:

JavaScript
const arr = [5, 4, 7, 2, 1]; const replaced = arr.with(2, 3); console.log(replaced); // [5, 4, 3, 2, 1] // Original not modified console.log(arr); // [5, 4, 7, 2, 1]

Final thoughts

They were other features but ES14 was all about easier functional programming and built-in immutability.

With the rise of React we’ve seen declarative JavaScript explode in popularity; it’s only natural that more of them come baked into the language as sweet syntactic sugar.

5 unnecessary VS Code extensions you should uninstall now

Can you count how many VS Code extensions you have right now?

Me: A whooping 56.

If you’re finding VS Code getting slower and more power-hungry with time, this number could well be the reason.

Because EVERY new extension added increases the app’s memory and CPU usage.

Coding is already challenging enough; Nobody need contend with this:

Maybe I should just give up music instead.

So we need to keep this number as low as possible to minimize this resource usage; ad also stopping these extensions from clashing with one another or with native functionality.

And you know, there’s a significant number of extensions in the Marketplace that provide functionality VSCode already has built-in.

Usually they were made when the feature wasn’t added yet; but once that happened they became largely redundant additions.

So below, I cover a list of these integrated VSCode features and extensions that provide them. Uninstalling these now dispensable extensions will increase your editor’s performance and efficiency.

I’ll be listing settings that control the behavior of these features. If you don’t know how to change settings, this guide will help.

Related: 10 Must-Have VSCode Extensions for Web Development

1. Auto closing of HTML tags

When you add a new HTML tag, this feature automatically adds the corresponding closing tag.

The closing tag for the div is automatically added.
The closing tag for the div is automatically added.

Extensions for this

These extensions add the auto-closing feature to VSCode:

  • Auto Close Tag (12.3M+ downloads): “Automatically add HTML/XML close tag, same as Visual Studio IDE or Sublime Text”.
  • Close HTML/XML Tag (344K downloads): “Quickly close last opened HTML/XML tag”.

But feature already built in

I use these settings to enable/disable the auto-closing of tags in VSCode:

  • HTML: Auto Closing Tags: “Enable/disable autoclosing of HTML tags”. It is true by default.
  • JavaScript: Auto Closing Tags: “Enable/disable automatic closing of JSX tags”. It is true by default.
  • TypeScript: Auto Closing Tags: “Enable/disable automatic closing of JSX tags”. It is true by default.
Settings for auto closing in the VSCode Settings UI.
Settings for auto-closing in the Settings UI.

Add the following to your settings.json file to turn them on:

settings.json

{
  "html.autoClosingTags": true,
  "javascript.autoClosingTags": true,
  "typescript.autoClosingTags": true
}

2. Path autocompletion

The path autocompletion feature provides a list of files in your project to choose from when importing a module or linking a resource in HTML.

Extensions for this

These extensions add the path autocompletion feature to VSCode:

  1. Path IntelliSense (12.5M+ downloads): “Visual Studio Code Plugin that autocompletes filenames”.
  2. Path Autocomplete (1.7M+ downloads): “Provides path completion for Visual Studio Code and VS Code for the web”.

But feature already built in

VS Code already has native path autocompletion.

When I type in a filename to import (typically when the opening quote is typed), a list suggested project files shows up for me to quickly choose from.

3. Snippets for HTML and CSS

These extensions help you save time by adding common HTML and CSS snippets using abbreviations you can easily recall.

Extensions for this

These extensions bring convenient HTML and/or CSS snippets to VSCode:

  • HTML Snippets (10.1M+ downloads): “Full HTML tags including HTML5 snippets”.
  • HTML Boilerplate (3.2M+ downloads): “A basic HTML5 boilerplate snippet generator”.
  • CSS Snippets (225K+ downloads): “Shorthand snippets for CSS”.

But feature already built-in

Emmet is a built-in VSCode feature that provides HTML and CSS snippets like these extensions. As you’ll see in the official VSCode Emmet guide, it’s enabled by default in html, haml, pug, slim, jsx, xml, xsl, css, scss, sass, less, and stylus files.

Comprehensive to say the least.

When you start typing an Emmet abbreviation, a suggestion will pop up with auto-completion options; You’ll also see a preview of the expansion as you type in the VSCode’s suggestion documentation fly-out (if it is open).

Using Emmet in VSCode.
Using Emmet in VSCode.

As you saw in the demo, this:

ol>li*3>p.rule$

turns into this:

<ol>
  <li>
    <p class="rule1">r</p>
  </li>
  <li>
    <p class="rule2"></p>
  </li>
  <li>
    <p class="rule3"></p>
  </li>
</ol>

Notice how similar the abbreviations are to CSS selectors. It’s by design; as stated on the official website, Emmet syntax is inspired by CSS selectors.

4. Bracket pair colorization

Bracket pair coloring is a popular syntax highlighting feature that colors brackets differently based on their order.

It makes it easier to identify scope and helps in writing expressions that involve many parentheses, such as single-statement function composition.

Extensions for this

Until VSCode had it built-in, these extensions helped enable the feature in the editor:

  1. Bracket Pair Colorizer 2 (6.1M+ downloads): “A customizable extension for colorizing matching brackets”. It has now been deprecated.
  2. Rainbow Brackets: (1.9M downloads): “A rainbow brackets extension for VS Code”.

I noticed Colorizer 2 has actually been deprecated since 2021 — wasn’t enough to stop millions from installing it every single year till date.

But feature already built in

After seeing the demand for bracket pair coloring and the performance issues involved in adding the feature as an extension, the VSCode team decided to integrate it into the editor.

In this blog, they say that the native bracket pair coloring feature is more than 10,000 times faster than Bracket Pair Colorizer 2.

Here’s the setting to enable/disable bracket pair colorization.

  • Editor > Bracket Pair Colorization: “Controls whether bracket pair colorization is enabled or not”. It is true by default, there’s been some debate about whether this should be the case here.
The bracket pair colorization option in the VSCode Settings UI.
The bracket pair colorization option in the Settings UI.

You can enable this by adding the following to your settings.json

settings.json

{
  "editor.bracketPairColorization.enabled": true
}

There is a maximum of 6 colors that can be used for successive nesting levels. Although each theme will have its maximum. For example, the Dracula theme has 6 colors by default, but the One Dark Pro theme has only 3.

Left: bracket pair colors in One Dark Pro theme. Right: bracket pair in Dracula theme.
Left: bracket pair colors in One Dark Pro theme. Right: bracket pair in Dracula theme.

Nevertheless, you can customize the bracket colors for any theme with the workbench.colorCustomizations setting.

  "workbench.colorCustomizations": {
    "[One Dark Pro]": {
      "editorBracketHighlight.foreground1": "#e78009",
      "editorBracketHighlight.foreground2": "#22990a",
      "editorBracketHighlight.foreground3": "#1411c4",
      "editorBracketHighlight.foreground4": "#ddcf11",
      "editorBracketHighlight.foreground5": "#9c15c5",
      "editorBracketHighlight.foreground6": "#ffffff",
      "editorBracketHighlight.unexpectedBracket.foreground": "#FF2C6D"
    }
  },

We specify the name of the theme in square brackets ([ ]), then we assign values to the relevant properties. The editorBracketHighlight.foregroundN property sets the color of the Nth set of brackets, and 6 is the maximum.

Now this will be the bracket pair colorization for One Dark Pro:

Customized bracket pair colorization for One Dark Pro theme.
Customized bracket pair colorization for One Dark Pro theme.

5. Auto importing of modules

With an auto-importing feature, when a function, variable, or some other member of a module is referenced in a file, the module is automatically imported into the file, saving time and effort.

The function is automatically imported from the file when referenced.
The function is automatically imported from the file when referenced.

If the module files are moved, the feature will help automatically update them.

Imports for a file are automatically updated on move.
Imports for a file are automatically updated on move.

Extensions for this

Here are some of the most popular extensions providing the feature for VSCode users:

  • Auto Import (3.8M downloads): “Automatically finds, parses, and provides code actions and code completion for all available imports. Works with Typescript and TSX”.
  • Move TS (810K downloads): “extension for moving typescript files and folders and updating relative imports in your workspace”.

But feature already built in

You can enable or disable auto-importing modules in VSCode with the following settings.

  • JavaScript > Suggest: Auto Imports: “Enable/disable auto import suggestions”. It is true by default.
  • TypeScript > Suggest: Auto Imports: “Enable/disable auto import suggestions”. It is true by default.
  • JavaScript > Update Imports on File Move: “Enable/disable automatic updating of import paths when you rename or move a file in VS Code”. The default value is prompt, meaning that a dialog is shown to you, asking if you want to update the imports of the moved file. Setting it to alwayswill cause the dialog to be skipped, and never will turn off the feature entirely.
  • TypeScript > Update Imports on File Move: “Enable/disable automatic updating of import paths when you rename or move a file in VS Code”. Like the previous setting, it has possible values of prompt, always, and never, and the default is prompt.
One of the auto import settings in the Settings UI.
One of the auto import settings in the Settings UI.

You can control these settings with these settings.json properties:

{
  "javascript.suggest.autoImports": true,
  "typescript.suggest.autoImports": true,
  "javascript.updateImportsOnFileMove.enabled": "prompt",
  "typescript.updateImportsOnFileMove.enabled": "prompt"
}

You can also add this setting if you want your imports to be organized any time the file is saved.

"editor.codeActionsOnSave": {
    "source.organizeImports": true
}

This will remove unused import statements and arrange import statements with absolute paths on top, providing a hands-off way to clean up your code.

Final thoughts

These extensions might have served a crucial purpose in the past, but not anymore for the most part, as much of the functionality they provide has been added as built-in VSCode features. Remove them to reduce the bloat and increase the efficiency of Visual Studio Code.

Why Devin AI can’t take your job.

Devin AI.

They claim it’s the silver bullet for all software creation, a miraculous tech outperforming every other AI model and handling real-world programming with ease.

With recent news of Nvidia CEO, Jensen Huang, confidently predicting the impending death of coding, surely this Devin AI thing must be the first nail to go in the coffin.

Mhmm.

Sounds suspiciously familiar… AutoGPT, anyone? GPT Engineer? LOL.

Oh no… before jumping on the bandwagon we need to take a closer look at the deception behind this supposed game-changer.

The first glaring issue is the lack of transparency surrounding Devin AI’s performance metrics.

Sure, they claim it’s superior, but how did they arrive at these numbers? And where’s the proof? There’s a conspicuous absence of generated source code to back up their claims.

Without this crucial evidence you can’t take their word at face value.

Can Devin AI really make a meaningful impact in a real-world repository? Doubtful. And what about limitations? Not a word. It’s as if they want us to believe Devin AI is flawless, without a single drawback.

The demos provided by Devin AI are suspect at best; They showcase its abilities but conveniently omit crucial details.
Ever notice how they never revealed the prompts inputted by the user?

If you pause the videos and examine the timestamps, you’ll find it takes hours, not the mere five minutes they lead you to believe. It’s a smoke and mirrors act designed to dazzle without substance.

And what about the demos themselves? They’re basic, rudimentary at best. Many of the problems showcased are nothing more than following a tutorial, some of which even included code snippets.

Hype over competence.

Perhaps the most concerning aspect is the lack of public testing. If Devin AI truly lives up to the hype, why not let the public put it through its paces?

The reluctance to release it for testing raises red flags and hints at a possible cash grab scheme. Business may well soon find themselves disillusioned with promises that fail to materialize.

Trusting AI blindly is a path to failure.

Even if Devin AI does possess remarkable capabilities, it’s important to remember that code still requires human understanding and review to be acceptable. Software engineering is a nuanced field with countless variables; How can an AI know it is correct when its idea of correctness is bound by its training data?

If you think AI can replace developers so easily, then you probably missing the whole point of why we code. Coding at it’s core, is not about typing and compiling. It’s not even about creating apps or websites.

Coding is about specifying the requirements of a system with zero ambiguity. It’s about expressing the solution to a problem with absolute precision.

When you type in a prompt to ChatGPT with all the vivid descriptions and (hopefully) expressive constraints, you are coding.

The difference now is the glaring ambiguity of natural language; the lack of certainty of getting exactly what you want from the AI 100% of the time. That’s why you can refine a prompt dozens of times and have absolutely nothing to show for it.

So AI can only be as good at generating code as the instructions it’s given. And describing the software you want with precision has always been the greatest challenge in software development.

If Devin AI can compel users to provide enough definitions, then perhaps it has potential. But until then, it remains an overhyped tool with limited utility.

AI’s role in programming is similar to the evolution of programming languages. As languages have progressed, programming has become more accessible. But has this led to fewer programmers? No. Instead, it has expanded the reach of programming, leading to more innovation and productivity.

Likewise AI-supported coding will enhance productivity, not replace developers. These AI models are essentially sophisticated search engines trained on vast amounts of data. They excel at common tasks but falter when faced with specific or innovative challenges. They lack the creativity and problem-solving abilities inherent in human developers.

Once again let’s not forget about reliability; AI may churn out code, but isn’t always accurate; deploying AI in critical applications without human oversight is a recipe for disaster. Developers are essential for identifying and correcting errors to ensure the integrity and functionality of the software.

Devin AI may have its uses but it’s far from the panacea it’s been made out to be. As software engineers we should embrace innovation but remain skeptical of overhyped technologies. After all, it’s our expertise and ingenuity that will continue to drive progress in the field, not flashy AI gimmicks.

Play Updates

Mar 30, 2024

  • Improved compatibility of option input and free-text input.
  • Fix issue where completed puzzle parts were not saving to the browser.

Mar 23, 2024

  • Fixed the issue where Mar 23, 2024 daily puzzle didn’t have the correct option.

Shuffling algorithm in 1 line instead of 10: functional Fisher-Yates

Can we write the Fisher-Yates shuffling algorithm in a declarative and functional way with zero mutations, all in a single one-liner statement?!

Let’s find out.

The normal way of writing Fisher-Yates

Some time ago I was creating an indie card game for Android and I needed to shuffle the cards before sharing them to the user and opponents.

Well I didn’t know about the standard shuffling algorithms, so after some thought I came up with this:

But after reading more about Fisher-Yates on Wikipedia, I discovered serious problems with my algorithm:

JavaScript
// It was in C#, but like this function shuffleArray(array) { const clone = [...array]; for (let i = clone.length - 1; i > 0; i--) { // Swap i with random item from 0..n const randomIndex = Math.floor( Math.random() * clone.length // ❌❌ ); const temp = clone[i]; clone[i] = clone[randomIndex]; clone[randomIndex] = temp; } return clone; } console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'I', 'L', 'O', 'G', 'C' ] console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'L', 'O', 'G', 'I', 'C' ]

I was swapping each item with a random element in the range of 0..n, but this was wrong.

As explained here, this made it more likely for the array to get shuffled in particular ways.

The right way was to use the range 0..i to swap with the current element i in the loop.

JavaScript
function shuffleArray(array) { const clone = [...array]; for (let i = clone.length - 1; i > 0; i--) { const randomIndex = Math.floor(Math.random() * (i + 1)); const item = clone[i]; clone[i] = clone[randomIndex]; clone[randomIndex] = item; } return clone; } console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'G', 'L', 'I', 'C', 'O' ] console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'L', 'O', 'G', 'I', 'C' ]

With this I could make sure every possible shuffled result would have an equal chance of happening.

Functional, immutable, one-liner Fisher-Yates

How exactly are we supposed to go about this?

Can we try this?👇

JavaScript
const shuffleArray = (arr) => arr.reduce((array, item, i) => { const randomIndex = Math.floor( Math.random() * arr.length ); [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; return arr; });

No we can’t; We used ES6 swapping to shorten the code but we still mutated the array. And cloning doesn’t count.

We need to figure out a way to swap the array elements immutably – create a new array with items swapped.

Using conditionals we can easily come up with this:

JavaScript
const immutableSwap = (arr, i, j) => arr.map((item, index) => index === i ? arr[j] : index === j ? arr[i] : item ); const arr = ['B', 'E', 'A', 'U', 'T', 'Y']; console.log(immutableSwap(arr, 2, 4)); // [ 'B', 'E', 'T', 'U', 'A', 'Y' ]

But we could also use Object.values like this:

JavaScript
const immutableSwap = (arr, i, j) => { return Object.values({ ...arr, [i]: arr[j], [j]: arr[i], }); }; console.log(immutableSwap(arr, 3, 5)); // [ 'B', 'Y', 'A', 'U', 'T', 'E' ]

What’s next?

With the immutable swap worked out, our game plan is pretty straightforward:

  1. For each item i in 0..n, immutably select an index in 0..i to swap with i for each i in 0..n
  2. Loop through the array again and immutably swap each element with its assigned swapping pair.

For #1 we can easily use map() to create an array with the new random positions for every index:

JavaScript
const getShuffledPosition = (arr) => { return [...Array(arr.length)].map((_, i) => Math.floor(Math.random() * (i + 1)) ); }; /* [0, 2, 0, 1] swap item at: 1. index 0 with index 0 2. index 1 with index 2 3. index 2 with index 0 4. index 3 with index 1 */ getShuffledPosition(['🔵', '🔴', '🟡', '🟢']); getShuffledPosition(['🔵', '🔴', '🟡', '🟢']); // [0, 1, 1, 3] getShuffledPosition(['🔵', '🔴', '🟡', '🟢']); // [0, 0, 2, 1]

What about #2?

We’re outputting an array, but we can’t use map again because the transformation at each step depends on the full array and not just the current element.

So we’ll use reduce():

JavaScript
const shuffleUsingPositions = (arr) => getShuffledPosition(arr).reduce( (toShuffle, newPosition, originalIndex) => immutableSwap(toShuffle, originalIndex, newPosition), arr ); shuffleUsingPositions(['🔵', '🔴', '🟡', '🟢']) // [ '🔴', '🔵', '🟢', '🟡' ]

Not let’s de-abstract the immutable functions so we have a true and complete one-liner:

JavaScript
const shuffleArray = (arr) => [...Array(arr.length)] .map((_, i) => Math.floor(Math.random() * (i + 1))) .reduce( (toShuffle, newPosition, originalIndex) => toShuffle.map((item, index) => index === originalIndex ? toShuffle[newPosition] : item === newPosition ? originalIndex : item ), arr );

Or – faster and more readable:

JavaScript
const shuffleArray = (arr) => [...Array(arr.length)] .map((_, i) => Math.floor(Math.random() * (i + 1))) .reduce( (toShuffle, newPosition, originalIndex) => Object.values({ ...toShuffle, [originalIndex]: toShuffle[newPosition], [newPosition]: toShuffle[originalIndex], }), arr );
JavaScript
shuffleArray(['🔵', '🔴', '🟡', '🟢']) // (3x) // [ '🟡', '🔴', '🔵', '🟢' ] // [ '🔴', '🟡', '🟢', '🔵' ] // [ '🟡', '🔵', '🔴', '🟢' ]

Just like you can always write a recursive algorithm as a loop, you can write every imperative iteration as a brilliant single-statement chain of array methods.

They all work perfectly.

VS Code: 5 rapid file creation tips for greater productivity

From painfully slow to lightning-fast, let’s look at all the 5 ways to create a file in VS Code.

And fastest way adds new files without having to use your mouse at all! We’ll see…

5. File > New File…

I’m pretty sure very few people use this apart from those who are new to text editors.

You move your mouse all the way up to File then click New File…

Then you’ve still got to enter the filename:

THEN, a file picker dialog for you to choose the folder – never mind VS Code having its own built-in file manager.

Before finally:

Create: New File

This is almost like the first, except you use the Create: New File from the Command Palette.

4. Double-click tab bar

Not many know about this method… double-clicking the file tab bar:

Ctrl + N

Or use the faster Ctrl + N keyboard shortcut.

So after Ctrl + N you either manually select a language:

Or you just start typing and wait for language auto-detection:

It’s useful when you don’t have any open project and you just want a quick file to work on.

You’re still got to save it though:

3. New File… icon button

This is one of the more popular ways; clicking the New File... icon button in the File Explorer Pane:

2. Double-click file explorer pane

This works great for top-level files.

1. A

Opening keyboard shortcuts like this:

And editing it like this:

To create files faster than ever at the single press of a key:

They all have different speeds but they’re all useful. VS Code’s vast versatility is unmatched.

17 lines of JS code became 1 line after this simple trick

How did these 17 lines of JavaScript code turn into a one-liner function?

It’s easy, you’ll see!

The 1st thing we notice is we’re selecting items from an array – this should NEVER take 3 lines in JavaScript!

We have the destructuring operator to turn this:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = []; for (const line of lines) { const sourceDestination = line.split(' '); const source = sourceDestination[2]; const destination = sourceDestination[3]; const redirectObj = { source: source, destination: destination, permanent: true, }; sourceDestinationList.push(redirectObj); } return sourceDestinationList; }

into this:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = []; for (const line of lines) { // ✅ Skip 1st two with comma placeholders! const [, , source, destination] = line.split(' '); const redirectObj = { source: source, destination: destination, permanent: true, }; sourceDestinationList.push(redirectObj); } return sourceDestinationList; }

What else is obvious? A loop and a gradual accumulation (with push).

Which means we’re easily cutting out statements here with map() or reduce():

reduce works well anywhere but map is the natural choice since 1 array clearly transforms to another here.

So we go from this:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = []; for (const line of lines) { const [, , source, destination] = line.split(' '); const redirectObj = { source: source, destination: destination, permanent: true, }; sourceDestinationList.push(redirectObj); } return sourceDestinationList; }

To this:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = lines.map((line) => { // ✅ const [, , source, destination] = line.split(' '); const redirectObj = { source: source, destination: destination, permanent: true, }; // ✅ return redirectObj; }); return sourceDestinationList; }

The declaration, for loop and push() have all been eradicated.

Now removing the temporary variable:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = lines.map((line) => { const [, , source, destination] = line.split(' '); return { source: source, destination: destination, permanent: true, }; }); return sourceDestinationList; }

And combining the last 2 statements in map‘s callback – without calling split() twice.

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = lines.map((line) => { return line.split(' ').reduce( (acc, curr, i) => { return i === 2 ? { ...acc, source: curr } : i === 3 ? { ...acc, destination: curr } : acc; }, { permanent: true } ); }); return sourceDestinationList; }

reduce() has a way of making your head spin!

Let’s go with this instead; much more readable:

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = lines .map((line) => { return line.split(' '); }) .map(([, , source, destination]) => { return { source: source, destination: destination, permanent: true, }; }); return sourceDestinationList; }

Now we can remove the callback braces, from (a) => { return { b; } } to (a) => ({ b }):

JavaScript
function extractRedirects(str) { const lines = str.split('\n'); const sourceDestinationList = lines .map((line) => line.split(' ')) .map(([, , source, destination]) => ({ source: source, destination: destination, permanent: true, })); return sourceDestinationList; }

All that’s left now is to remove the 2 remaining temp vars and use implicit property values:

JavaScript
function extractRedirects(str) { return str .split('\n') .map((line) => line.split(' ')) .map(([, , source, destination]) => ({ source, // ✅ destination, // ✅ permanent: true, })); }

Arrow function:

JavaScript
const extractRedirects = (str) => str .split('\n') .map((line) => line.split(' ')) .map(([, , source, destination]) => ({ source, destination, permanent: true, }));

Yes refactors are fun; it’s fun to see the function gradually evolve and grow in a compact one-liner form.

And it wasn’t just for fancy; With it I moved a GIGANTIC amount of redirect information…

From painful, dinosaur WP Apache .htaccess:

They were way more than 5!

To lovely, modern next.config.js

And it worked perfectly.

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.