DEV Community

JW SOLUTIONS
JW SOLUTIONS

Posted on Originally published at jw-solutions.com

>-

Modernizing Legacy Code in JavaScript ES2024+

JavaScript development has evolved significantly beyond constructor functions and simulated privacy using underscores (_privateProperty) or lexical closures. With ES2024+ standards established, combining true private class fields (prefixed with #), static initialization blocks (static {}), and strict JSDoc typing allows teams to maintain robust, readable, and self-documenting codebases in pure JavaScript without mandatory TypeScript build steps.

The Problem: Legacy Patterns and Weak Encapsulation

In ES5 and early ES6 legacy codebases, state protection was governed purely by convention. This convention frequently led to tight coupling, scope leakages, and inadequate support from static analysis tools like ESLint.

// Legacy Code (Before)
function UserManager(initialUsers) {
  this._users = initialUsers || [];
  this._accessCount = 0;
}

UserManager.prototype.addUser = function(user) {
  if (user && user.id) {
    this._users.push(user);
    this._accessCount++;
    return true;
  }
  return false;
};
Enter fullscreen mode Exit fullscreen mode

This pattern leaves _users open to unintended external mutations and offers zero type clarity regarding arguments or return values.

The Modernized Solution: ES2024+ and Advanced JSDoc

Modern ES2024 syntax encapsulates state securely at the JavaScript engine runtime level using #. Combined with static initialization, setup logic stays grouped cleanly within the class boundary.

/**
 * @typedef {Object} User
 * @property {string} id - Unique identifier.
 * @property {string} name - Full user name.
 */

/**
 * Core user management system.
 * @template {User} T
 */
class UserManager {
  #users = new Map();
  #accessCount = 0;
  static #MAX_CAPACITY;

  static {
    // Complex immutable static initialization
    UserManager.#MAX_CAPACITY = 1000;
  }

  /**
   * @param {T[]} [initialUsers=[]] - Initial user dataset.
   */
  constructor(initialUsers = []) {
    for (const user of initialUsers) {
      this.addUser(user);
    }
  }

  /**
   * Adds a user if maximum capacity has not been breached.
   * @param {T} user - User record to append.
   * @returns {boolean} True if operation succeeded.
   */
  addUser(user) {
    if (!user?.id || this.#users.size >= UserManager.#MAX_CAPACITY) {
      return false;
    }
    this.#users.set(user.id, Object.freeze({ ...user }));
    this.#accessCount++;
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Formatting Standards & Tooling Integration

To ensure consistency, pair Prettier with ESLint utilizing modern parser rules. Add prettier-plugin-jsdoc to format JSDoc annotations automatically and enable eslint-plugin-jsdoc to validate documentation completeness across your repository.

ES2024 Refactoring Checklist

  • Replace convention-based _ properties with true # private fields.
  • Relocate static setup logic into class static {} initialization blocks.
  • Decorate classes and methods with complete JSDoc @param, @returns, and @typedef metadata.
  • Validate formatting and linting via Prettier and ESLint in your CI/CD pipeline.

Try it interactively: Format, validate, and auto-document your javascript directly in JW Solutions Code Formatter & Auto-Doc AI.
Originally published at jw-solutions.com.

Top comments (0)