javascriptnotesAdditional Concepts

JavaScript Loading

  • <script> is the default way of including JavaScript. The browser blocks HTML parsing while the script is being downloaded and executed. The browser will not continue rendering the page until the script has finished executing.
  • <script async> downloads the script asynchronously, in parallel with parsing the HTML. Executes the script as soon as it is available, potentially interrupting the HTML parsing. <script async> do not wait for each other and execute in no particular order.
  • <script defer> downloads the script asynchronously, in parallel with parsing the HTML. However, the execution of the script is deferred until HTML parsing is complete, in the order they appear in the HTML.
  • Use <script> for critical scripts that the page relies on to render properly.
  • Use <script async> when the script is independent of any other scripts on the page, for example, analytics and ads script
  • If a script relies on a fully-parsed DOM, the defer attribute will be useful in ensuring that the HTML is fully parsed before executing.

Rest and spread

Spread Syntax

ES2015’s spread syntax is very useful when coding in a functional paradigm as we can easily create copies of / merge arrays or objects without resorting to Object.create, Object.assign, Array.prototype.slice, or a library function. This language feature is used often in Redux and RxJS projects. Spread syntax (...) allows an iterable (like an array or string) to be expanded into individual elements. This is often used as a convenient and modern way to create new arrays or objects by combining existing ones.

OperationTraditionalSpread
Array cloningarr.slice()[...arr]
Array mergingarr1.concat(arr2)[...arr1, ...arr2]
Object cloningObject.assign({}, obj){ ...obj }
Object mergingObject.assign({}, obj1, obj2){ ...obj1, ...obj2 }
Copying arrays/objects

The spread syntax provides a concise way to create copies of arrays or objects without modifying the originals. This is useful for creating immutable data structures. However do note that arrays copied via the spread operator are shallowly-copied.

// Copying arrays
const array = [1, 2, 3];
const newArray = [...array];
console.log(newArray); // Output: [1, 2, 3]
 
// Copying objects
const obj = { name: "John", age: 30 };
const newObj = { ...person, city: "New York" };
console.log(newObj); // Output: { name: 'John', age: 30, city: 'New York' }
Merging arrays/objects

The spread syntax allows you to merge arrays or objects by spreading their elements/properties into a new array or object.

// Merging arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // Output: [1, 2, 3, 4, 5, 6]
 
// Merging objects
const obj1 = {
  foo: "bar",
};
 
const obj2 = {
  qux: "baz",
};
 
const merged = { ...obj1, ...obj2 };
console.log(copyOfTodd); // Output: { foo: "bar", qux: "baz" }
Passing arguments to functions

Use the spread syntax to pass an array of values as individual arguments to a function, avoiding the need for apply().

const numbers = [1, 2, 3];
Math.max(...numbers); // Same as Math.max(1, 2, 3)
Array vs object spreads

Only iterable values like Arrays and Strings can be spread in an array. Trying to spread non-iterables will result in a TypeError.

Spreading object into array:

const person = {
  name: "Todd",
  age: 29,
};
const array = [...person]; // Error: Uncaught TypeError: person is not iterable

On the other hand, arrays can be spread into objects.

const array = [1, 2, 3];
const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }

Rest Syntax

The rest syntax (...) in JavaScript allows you to represent an indefinite number of elements as an array or object. It is like an inverse of the spread syntax, taking data and stuffing it into an array rather than unpacking an array of data, and it works in function arguments, as well as in array and object destructuring assignments.

Rest parameters in functions
The rest syntax can be used in function parameters to collect all remaining arguments into an array. This is particularly useful when you don't know how many arguments will be passed to the function.
function addFiveToABunchOfNumbers(...numbers) {
  return numbers.map((x) => x + 5);
}
 
const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10);
console.log(result); // Output: [9, 10, 11, 12, 13, 14, 15]

Provides a cleaner syntax than using the arguments object, which is unsupported for arrow functions and represents all arguments whereas the usage of the rest syntax below allows remaining to represent the 3rd argument and beyond.

const [first, second, ...remaining] = [1, 2, 3, 4, 5];
console.log(first); // Output: 1
console.log(second); // Output: 2
console.log(remaining); // Output: [3, 4, 5]

Note that the rest parameters must be at the end. The rest parameters gather all remaining arguments, so the following does not make sense and causes an error:

function addFiveToABunchOfNumbers(arg1, ...numbers, arg2) {
  // Error: Uncaught Rest element must be last element.
}
Array destructuring

The rest syntax can be used in array destructuring to collect the remaining elements into a new array.

const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4]
Object destructuring

The rest syntax can be used in object destructuring to collect the remaining properties into a new object.

const { e, f, ...others } = {
  e: 1,
  f: 2,
  g: 3,
  h: 4,
}; // e: 1, f: 2, others: { g: 3, h: 4 }

Polyfills

Polyfills in JavaScript are pieces of code (usually JavaScript) that provide modern functionality on older browsers that do not natively support it. They enable developers to use newer features of the language and APIs while maintaining compatibility with older environments.

How polyfills work

Polyfills detect if a feature or API is missing in a browser and provide a custom implementation of that feature using existing JavaScript capabilities. This allows developers to write code using the latest JavaScript features and APIs without worrying about browser compatibility issues.

Example

For example, let’s consider the Array.prototype.includes() method, which determines if an array includes a specific element. This method is not supported in older browsers like Internet Explorer 11. To address this, we can use a polyfill:

// Polyfill for Array.prototype.includes()
if (!Array.prototype.includes) {
  Array.prototype.includes = function (searchElement) {
    for (var i = 0; i < this.length; i++) {
      if (this[i] === searchElement) {
        return true;
      }
    }
 
    return false;
  };
}

By including this polyfill, we can safely use Array.prototype.includes() even in browsers that don’t support it natively.

Implementing polyfills

  1. Identify the missing feature: Determine if the feature is compatible with the target browsers or detect its presence using feature detection methods like typeof, in, or window.
  2. Write the fallback implementation: Develop the fallback implementation that provides similar functionality, either using a pre-existing polyfill library or pure JavaScript code.
  3. Test the polyfill: Thoroughly test the polyfill to ensure it functions as intended across different contexts and browsers.
  4. Implement the polyfill: Enclose the code that uses the missing feature in an if statement that checks for feature support. If not supported, run the polyfill code instead.

Considerations

  • Selective loading: Polyfills should only be loaded for browsers that need them to optimize performance.
  • Feature detection: Perform feature detection before applying a polyfill to avoid overwriting native implementations or applying unnecessary polyfills.
  • Size and performance: Polyfills can increase the JavaScript bundle size, so minification and compression techniques should be used to mitigate this impact.
  • Existing libraries: Consider using existing libraries and tools that offer comprehensive polyfill solutions for multiple features, handling feature detection, conditional loading, and fallbacks efficiently

Libraries and services for polyfills

  • core-js: A modular standard library for JavaScript which includes polyfills for a wide range of ECMAScript features.

    import "core-js/actual/array/flat-map"; // With this, Array.prototype.flatMap is available to be used.
     
    [1, 2].flatMap((it) => [it, it]); // => [1, 1, 2, 2]
  • Polyfill.io: A service that provides polyfills based on the features and user agents specified in the request.

    <script src="https://polyfill.io/v3/polyfill.min.js"></script>

Regular Expressions

  1. Write them between two forward slashes

  2. Regex test method:

    var regex = /this/;
    regex.test(string1);
  3. Regex modifiers

    var string1 = "This is the longest string ever.";
    var string2 = "This is the shortest string ever.";
    var string3 = "Is this the thing called Mount Everest?";
    var string4 = "This is the Sherman on the Mount.";
     
    var regex = /this/;
     
    regex.test(string1);
    regex.test(string2);
    regex.test(string3);
    regex.test(string4);
     
    regex = /this/i; // case insensitive
     
    regex = /^this/i; // matches beginning of string
     
    regex = /this$/i; // matches end of string
     
    regex = /ever.$/i; // dot here matches "any character"
     
    regex = /ever\.$/i; // literal dot, escaped with a back slash
     
    // More info:
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
    // http://regex.info/book.html
  4. Regex replace method:

const regex = /[^a-zA-Z0-9]/g;
const clean = s.replace(regex, "").toLowerCase();
  1. Regex match method:
const s = "23+2-1";
const regex = /[+-]|\d+/g;
const clean = s.match(regex); // ["23", "+", "2", "-", "1"]

Intl namespace object

The Intl namespace object in JavaScript is a part of the ECMAScript Internationalization API, which provides language-sensitive string comparison, number formatting, and date and time formatting. This is particularly useful for applications that need to support multiple languages and regions.

Language-sensitive string comparison

The Intl.Collator object is used for comparing strings in a locale-aware manner. This is useful for sorting strings in a way that is consistent with the conventions of a particular language.

const collator = new Intl.Collator("de-DE");
console.log(collator.compare("ä", "z")); // Outputs a negative number because 'ä' comes before 'z' in German

Number formatting

The Intl.NumberFormat object is used for formatting numbers according to the conventions of a specific locale. This includes formatting for currency, percentages, and plain numbers.

const number = 1234567.89;
const formatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR",
});
console.log(formatter.format(number)); // Outputs '1.234.567,89 €'

Date and time formatting

The Intl.DateTimeFormat object is used for formatting dates and times according to the conventions of a specific locale.

const date = new Date();
const formatter = new Intl.DateTimeFormat("en-GB", {
  year: "numeric",
  month: "long",
  day: "numeric",
});
console.log(formatter.format(date)); // Outputs date in 'DD Month YYYY' format

Plural rules

The Intl.PluralRules object is used to get the plural form of a number in a specific locale. This is useful for correctly pluralizing words in different languages.

const pluralRules = new Intl.PluralRules("en-US");
console.log(pluralRules.select(1)); // Outputs 'one'
console.log(pluralRules.select(2)); // Outputs 'other'