Uncharted Territory: Exploring Lesser-Known JavaScript Functions

Photo by Growtika on Unsplash

Uncharted Territory: Exploring Lesser-Known JavaScript Functions

Unlocking Unpopular Functions in JavaScript for Better Code

Using + to Convert Strings to Numbers:

const numericValue = +"42"; // Converts the string to a number
console.log(numericValue); // Output: 42 

const numericValue = +"abc";
console.log(numericValue); // Output: Nan

The + symbol is placed before the stringNumber, and it effectively coerces the string into a numeric value. It's important to note that if the string cannot be converted to a valid number, the result will be NaN

Double Bitwise NOT for Math.floor:

// Instead of
const rounded1 = Math.floor(4.9);
console.log(rounded1); // Output: 4
// Use
const rounded2 = ~~4.9;
console.log(rounded2); // Output: 4

Swapping Values:

// Use destructuring
let a = 5, b = 10;
[a, b] = [b, a];

Destructuring assignment can be used to exchange the values of two variables in a concise manner, as shown in the example: [a, b] = [b, a];. This eliminates the need for a temporary variable and simplifies the swapping process.

Simplify Multiple Criteria Checks with Array.includes

Without Array.includes:

const userRole = "admin";

// Check if the user role is one of the specified roles
if (userRole === "admin" || userRole === "editor" || userRole === "viewer") {
  console.log("User has a valid role!");
} else {
  console.log("Invalid user role.");
}

With Array.includes:

const validRoles = ["admin", "editor", "viewer"];
const userRole = "admin";

// Check if the user role is in the array of valid roles
if (validRoles.includes(userRole)) {
  console.log("User has a valid role!");
} else {
  console.log("Invalid user role.");
}

Object.freeze and Immutability:

// Create a mutable object
const mutableObject = {
  property1: 'value1',
  property2: 'value2'
};
// Freeze the object to make it immutable
const immutableObject = Object.freeze(mutableObject);
// Attempt to modify a property - this will not throw an error, but it won't have any effect
immutableObject.property1 = 'new value';
// Attempt to add a new property - this will not throw an error, but it won't have any effect
immutableObject.property3 = 'value3';
// Attempt to delete a property - this will not throw an error, but it won't have any effect
delete immutableObject.property2;
// The original object remains unchanged
console.log(mutableObject); // { property1: 'new value', property2: 'value2' }
// The frozen object remains unchanged
console.log(immutableObject); // { property1: 'value1', property2: 'value2' }

Object.freeze makes an object immutable, preventing changes to its properties.

Although Object.freeze is handy for preventing changes to an object's properties, it's important to keep in mind that it doesn't ensure deep immutability. If your object has nested objects, those nested parts might still be mutable. If you need full deep immutability, you might want to consider taking extra steps or using libraries like Immutable.js, which specifically support immutable data structures.

Logical AND for Short-Circuit Evaluation:

// Instead of if-else
let result;
if (condition) {
  result = 'True';
} else {
  result = 'False';
}

// Use logical AND
let result = condition && 'True' || 'False';

Early Returns & Less Nesting

Less Desirable Example:

function calculatePrice(quantity, price) {
  if (quantity > 0) {
    if (price > 0) {
      let totalPrice = quantity * price;

      if (quantity >= 10) {
        totalPrice *= 0.9; // 10% discount for quantities of 10 or more
      }

      return totalPrice;
    } else {
      console.error("Invalid input: Price must be a positive value.");
    }
  } else {
    console.error("Invalid input: Quantity must be a positive value.");
  }
}
const finalPrice = calculatePrice(15, 5);

Good Example:

function calculatePrice(quantity, price) {
  // Validate input parameters
  if (quantity <= 0 || price <= 0) {
    console.error("Invalid input: Quantity and price must be positive values.");
    return;
  }

  // Calculate total price
  const totalPrice = quantity * price;

  // Apply discount for bulk purchases
  if (quantity >= 10) {
    return totalPrice * 0.9; // 10% discount for quantities of 10 or more
  }

  return totalPrice;
}
const finalPrice = calculatePrice(15, 5);

The "Less Nesting, Return Early" coding style provides benefits such as improved readability, better error handling, and easier debugging. It simplifies code structure and makes it more straightforward to understand and maintain.

Nullish Coalescing Operator (??):

// Instead of logical OR
const value = (someVariable !== null && someVariable !== undefined) ? someVariable : 'default';
// Use nullish coalescing operator
const value = someVariable ?? 'default';

The Nullish Coalescing Operator (??) efficiently provides default values for variables, ensuring the right-hand operand is used only when the left-hand operand is null or undefined.

Often underutilized Array methods in JavaScript:

  1. flat and flatMap:

    • flat: Flattens nested arrays.

    • flatMap: Maps each element using a mapping function and then flattens the result.

    let nestedArray = [1, [2, [3]]];
    let flatArray = nestedArray.flat(); // [1, 2, [3]]
    let mappedFlatArray = nestedArray.flatMap(value => [value * 2]); // [2, 4, 6]
  1. from with a Mapping Function:

    • Creates a new array with the results of calling a provided function on every element in the array.
    let array = [1, 2, 3];
    let squaredArray = Array.from(array, x => x * x); // [1, 4, 9]
  1. Reverses the elements of an array in place.

     let array = [1, 2, 3, 4, 5];
     array.reverse(); // [5, 4, 3, 2, 1]
    
  2. Array.some: Checks if at least one element in the array satisfies a provided condition.

     let hasPositiveNumber = [-1, 0, 1, 2].some(num => num > 0); // true
    
  3. Array.every: Checks if all elements in the array satisfy a provided condition.

     let allPositiveNumbers = [1, 2, 3, 4].every(num => num > 0); // true
    
  4. Array.reduceRight: Applies a function against an accumulator and each element in the array (from right to left) to reduce it to a single value.

     let reversedString = ['a', 'b', 'c'].reduceRight((acc, char) => acc + char, ''); // 'cba'
    

Lesser-known examples of using toLocaleString for various scenarios:

  1. Basic Number Formatting:

     let number = 1234567.89;
     let formattedNumber = number.toLocaleString(); // "1,234,567.89"
    
  2. Currency Formatting:

     let price = 12345.67;
     let formattedPrice = price.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); // "$12,345.67"
    
  3. Date Formatting:

     let date = new Date('2023-01-01T12:34:56');
     let formattedDate = date.toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' });
     // "Sunday, January 1, 2023 at 12:34 PM"
    
  4. Percentage Formatting:

     let percentage = 0.456;
     let formattedPercentage = percentage.toLocaleString('en-US', { style: 'percent' }); // "45.6%"
    
  5. Grouping Options:

     let largeNumber = 9876543210;
     let formattedLargeNumber = largeNumber.toLocaleString('en-US', { useGrouping: false }); // "9876543210"
    
  6. Compact Display for Large Numbers:

     let largeNumber = 12345678901234567890;
     let formattedLargeNumber = largeNumber.toLocaleString('en-US', { notation: 'compact' }); // "12.3Q"
    
  7. Customizing Decimal and Thousand Separators:

     let customFormattedNumber = 9876543.21.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 4 });
     // "9,876,543.2100"
    
  8. Using Locales for Different Regions:

     let number = 12345.67;
     let formattedNumberFrench = number.toLocaleString('fr-FR'); // "12 345,67"
     let formattedNumberGerman = number.toLocaleString('de-DE'); // "12.345,67"