Shocking Things You Can Do in JavaScript
Max Prilutskiy

Max Prilutskiy @maxprilutskiy

About: Hacking on open source, with JavaScript and LLMs. Thoughts on software development, and unusual tech stuff.

Location:
San Francisco, California
Joined:
Aug 18, 2021

Shocking Things You Can Do in JavaScript

Publish Date: May 24
46 10

Think you know JavaScript? I thought I did too, until I discovered features that completely changed how I write code. During a recent pair programming session, my teammate watched in disbelief as I transformed a 30-line utility function into three lines of native JavaScript.

"Wait, since when can you do that?" he asked. That moment made me realize how many of us developers are missing out on JavaScript's most powerful capabilities.

At Lingo.dev, we're constantly exploring the edges of what's possible with modern web. When I shared these discoveries with my dev friends, even the senior developers were surprised by some of these features. That's when I knew I had to write this article.

The truth is, JavaScript has quietly evolved into a language with shocking capabilities that eliminate the need for many popular libraries. These aren't experimental features or proposals - they're stable, cross-platform functionalities available right now that will make you rethink what's possible in vanilla JavaScript.

So after my last article on surprising HTML5 elements got such a positive response, I realized I'd share more hidden gems. And after 20 years of coding, I'm still finding JavaScript features that make me do a double-take. Just like when I discovered you could create modal windows with pure CSS and no JavaScript, these JavaScript capabilities show how powerful modern web standards have become.

Set operations that make lodash obsolete

I used to reach for lodash whenever I needed to compare arrays or find unique values. Not anymore. Modern JavaScript Sets now include native methods for all the set operations you'd expect from a proper programming language.

// Creating sets of programming languages
const frontendLangs = new Set(['JavaScript', 'TypeScript', 'HTML', 'CSS']);
const backendLangs = new Set(['Python', 'JavaScript', 'Java', 'Go', 'Ruby']);

// Find languages used in both frontend and backend (intersection)
const fullstackLangs = frontendLangs.intersection(backendLangs);
console.log([...fullstackLangs]); // ['JavaScript']

// Find languages unique to frontend (difference)
const frontendOnlyLangs = frontendLangs.difference(backendLangs);
console.log([...frontendOnlyLangs]); // ['TypeScript', 'HTML', 'CSS']
Enter fullscreen mode Exit fullscreen mode

I've lost count of how many times I've written utility functions to find the intersection or difference between arrays. Now it's built right into the language with intersection(), difference(), union(), and even symmetricDifference(). There are also predicate methods like isSubsetOf(), isSupersetOf(), and isDisjointFrom() that make set comparisons trivial.

What's particularly elegant about these methods is that they work with any "set-like" object that provides a size property, a has() method, and a keys() iterator. This means you can use Maps as sets of keys, making these operations even more versatile than their lodash counterparts.

Non-destructive array methods that respect immutability

Working with immutable data has always been a pain point in JavaScript. We've written countless utility functions and reached for libraries like Immer just to avoid mutating our arrays. Now JavaScript has native solutions with the "to-" prefixed array methods.

// Original array of user scores
const scores = [82, 95, 68, 90, 77];

// Sort scores without modifying original array
const sortedScores = scores.toSorted((a, b) => b - a);
console.log('Original scores:', scores); // [82, 95, 68, 90, 77]
console.log('Sorted scores (high to low):', sortedScores); // [95, 90, 82, 77, 68]

// Replace a score without modifying original array
const updatedScores = scores.with(2, 73); // Replace index 2 (value 68) with 73
console.log('Updated scores:', updatedScores); // [82, 95, 73, 90, 77]
Enter fullscreen mode Exit fullscreen mode

These methods mirror their mutating counterparts but return new arrays instead: toSorted() instead of sort(), toReversed() instead of reverse(), toSpliced() instead of splice(), and with() as a non-destructive version of bracket assignment.

The beauty of these methods is that they can be chained together for complex transformations while preserving your original data. No more defensive copying with [...array] or array.slice() before every operation.

Object grouping that transforms your data instantly

Ever needed to group an array of objects by some property? I used to write a reducer function every single time. Now there's a built-in solution that's both elegant and powerful.

// A collection of products
const products = [
  { name: "Laptop", category: "Electronics", price: 1200, inStock: true },
  { name: "Headphones", category: "Electronics", price: 100, inStock: true },
  { name: "Coffee Maker", category: "Kitchen", price: 80, inStock: false },
  { name: "Running Shoes", category: "Apparel", price: 120, inStock: true },
  { name: "Blender", category: "Kitchen", price: 60, inStock: true },
  { name: "T-shirt", category: "Apparel", price: 25, inStock: false }
];

// Group products by category
const productsByCategory = Object.groupBy(products, product => product.category);
Enter fullscreen mode Exit fullscreen mode

This gives you an object with keys for each category and arrays of matching products as values. It's the kind of operation that used to require a reduce function with a bunch of conditionals, but now it's a one-liner.

What makes this feature particularly powerful is its flexibility. You can group by any computed value, not just object properties. Want to group by price range? Just return a string like 'Budget', 'Mid-range', or 'Premium' from your callback. Need to group by multiple criteria? Return a composite key. The possibilities are endless.

Text segmentation that actually understands language

If you've ever worked with internationalization, you know that splitting text by spaces doesn't work for many languages. Japanese, Chinese, Thai, and others don't use spaces between words, making proper text segmentation a nightmare. Enter Intl.Segmenter.

// Text segmentation for languages without spaces between words
const japaneseText = "吾輩は猫である。名前はまだない。";

// Create a word segmenter for Japanese
const japaneseWordSegmenter = new Intl.Segmenter('ja-JP', { granularity: 'word' });

// Segment Japanese text into words
const japaneseWords = [...japaneseWordSegmenter.segment(japaneseText)]
  .filter(segment => segment.isWordLike)
  .map(segment => segment.segment);

console.log(japaneseWords);
// ['吾輩', 'は', '猫', 'である', '名前', 'は', 'まだ', 'ない']
Enter fullscreen mode Exit fullscreen mode

This feature is a game-changer for multilingual applications. You can segment text by graphemes (characters), words, or sentences, with full support for the nuances of each language. It's the kind of functionality that used to require specialized libraries or server-side processing.

The API is surprisingly simple yet powerful. You create a segmenter with a locale and granularity, then call its segment() method on any string. The result is an iterator that gives you each segment along with metadata like whether it's a word or punctuation.

At Lingo.dev, we've been using this a lot deep inside our localization API, and it's dramatically simplified our text processing pipeline. What used to require multiple specialized libraries and server-side processing can now be handled directly in the browser with just a few lines of code.

Logical assignment operators that simplify common patterns

JavaScript has always had compound assignment operators like += and *=, but the logical assignment operators take this concept to a new level by combining logical operations with assignment.

// User preferences with defaults
let userPrefs = {
  theme: 'light',
  notifications: false,
  // fontSize is not set
};

// Function to update preferences
function updatePreferences(newPrefs) {
  // Only set theme if it's not already set (nullish coalescing assignment)
  userPrefs.theme ??= newPrefs.theme;

  // Enable notifications if they're currently disabled (logical AND assignment)
  userPrefs.notifications &&= newPrefs.notifications;

  // Set fontSize if it's not set, or keep current value if it exists (nullish coalescing assignment)
  userPrefs.fontSize ??= newPrefs.fontSize;

  return userPrefs;
}
Enter fullscreen mode Exit fullscreen mode

The three logical assignment operators are:

  • ??= (nullish coalescing assignment): Assigns only if the left side is null or undefined
  • &&= (logical AND assignment): Assigns only if the left side is truthy
  • ||= (logical OR assignment): Assigns only if the left side is falsy

These operators eliminate common patterns like x = x || y or if (x === null || x === undefined) x = y, making your code more concise and readable. They're particularly useful for setting default values, conditionally updating flags, and implementing short-circuit logic.

Top-level await that simplifies async module initialization

Asynchronous code has always been a bit awkward in JavaScript, especially when it comes to module initialization. Before top-level await, you had to use immediately invoked async functions or promise chains just to load data during module initialization. Now you can use await directly at the top level of a module.

// In a module file (e.g., dataService.js)

// Fetch configuration before module initialization
const config = await fetch('/api/config').then(res => res.json());

// Initialize database connection
const dbConnection = await initDatabase(config.dbUrl);

// Export initialized services
export const userService = new UserService(dbConnection);
export const productService = new ProductService(dbConnection, config.apiKeys);
Enter fullscreen mode Exit fullscreen mode

This feature fundamentally changes how we structure applications. Modules can now handle their own initialization, and importers don't need to worry about whether a module is ready to use. The JavaScript runtime takes care of resolving all dependencies in the correct order.

Top-level await also enables dynamic module loading based on runtime conditions. You can fetch configuration, check feature flags, or perform authentication before deciding which modules to load. This level of flexibility was previously impossible without complex bootstrapping code.

Private class features that enable true encapsulation

JavaScript classes have always lacked true encapsulation. Developers resorted to naming conventions like _privateProperty or closures to hide implementation details, but these approaches were either not enforced or cumbersome. Private class features solve this problem elegantly.

class BankAccount {
  // Private fields
  #balance = 0;
  #transactions = [];
  #interestRate = 0.01;
  #accountNumber;

  constructor(initialDeposit, accountNumber) {
    this.#accountNumber = accountNumber;
    if (initialDeposit > 0) {
      this.deposit(initialDeposit);
    }
  }

  // Public methods
  deposit(amount) {
    if (amount <= 0) throw new Error('Deposit amount must be positive');
    this.#balance += amount;
    this.#addTransaction('deposit', amount);
    return this.balance;
  }

  // Private method
  #addTransaction(type, amount) {
    this.#transactions.push({
      type,
      amount,
      date: new Date(),
      balance: this.#balance
    });
  }

  // Getter for read-only access to private field
  get balance() {
    return this.#balance;
  }
}
Enter fullscreen mode Exit fullscreen mode

The # prefix denotes private fields, methods, and accessors that are only accessible within the class itself. Any attempt to access these members from outside the class results in a syntax error, providing true encapsulation that's enforced by the language.

This feature transforms how we design classes in JavaScript. You can now clearly separate public API from implementation details, prevent external code from depending on internals, and refactor with confidence knowing that private members are truly private.

Atomics and SharedArrayBuffer for true parallelism

JavaScript has always been single-threaded, with Web Workers providing concurrency but not shared memory. Atomics and SharedArrayBuffer change this paradigm, enabling true parallel programming with shared memory.

// Create a shared buffer that can be accessed by multiple threads
const buffer = new SharedArrayBuffer(4); // 4 bytes
const view = new Int32Array(buffer); // View as a single 32-bit integer

// Initialize the value
view[0] = 0;

// Create a worker thread
const worker = new Worker('./atomic-worker.js');

// Send the shared buffer to the worker
worker.postMessage({ buffer });

// Increment the shared value 1000 times
for (let i = 0; i < 1000; i++) {
  // Atomically add 1 to the value at index 0
  Atomics.add(view, 0, 1);
}
Enter fullscreen mode Exit fullscreen mode

The Atomics API provides operations that execute as a single, uninterruptible unit, preventing race conditions when multiple threads access shared memory. This enables high-performance parallel computing directly in the browser, opening up possibilities for applications that were previously impossible in JavaScript.

You can implement sophisticated synchronization primitives like mutexes, semaphores, and barriers using Atomics. This brings JavaScript closer to languages like Java and C++ in terms of parallel programming capabilities, while maintaining the safety guarantees that prevent common concurrency bugs.

JavaScript continues to surprise

JavaScript continues to surprise me, even after years of working with it daily. These features represent just a fraction of what modern JavaScript can do without external dependencies. By embracing these capabilities, we can write cleaner, more maintainable code while reducing our bundle sizes.

When we explore these kinds of native solutions at Lingo.dev, we often find they outperform the third-party alternatives we've relied on for years. There's something elegant about leveraging what the language itself provides rather than reaching for yet another package.

Next time you're about to install a utility library, take a moment to check if JavaScript already has what you need. You might be shocked by what you find.


Useful links:

Comments 10 total

Add comment