FDT Additional Topics

Modified on Fri, 28 Jul, 2023 at 1:35 PM

Variables in Javascript


What is it?

In JavaScript, a variable is a symbolic name for a value and can be thought of as a container that holds information.

Where is it used?

Variables are used throughout JavaScript programming, whether it is in the browser or in Node.js for server-side applications. They are used to store and manipulate data during the execution of a program.

How is it used?

  1. Declaration: First, you declare a variable using one of the three keywords: varlet, or const.

  2. Initialization: You can then initialize the variable by assigning it a value using the equals (=) operator.

  3. Usage: After the variable has been declared and initialized, you can use it in your code to perform various operations.

Code snippet to explain the same

Here's a simple example:

let name = "John"; // declare and initialize a variable
console.log(name); // use the variable

const pi = 3.14; // declare and initialize a constant variable
console.log(pi); // use the constant variable

var age = 30; // declare and initialize a variable using 'var'
console.log(age); // use the variable


Takeaways / best practices

  1. Choose the right keyword: Use let for variables that will change, const for variables that won't change, and avoid var as it has more complex and less predictable behavior due to its function scope and hoisting characteristics.

  2. Naming: Use meaningful names for your variables to make your code more readable and maintainable.

  3. Camel Case: In JavaScript, the convention is to use camel case for variable names (e.g., myVariableName).

  4. Initialization: Always initialize your variables. Uninitialized variables have a default value of undefined which can lead to unexpected behavior in your program.

  5. Scope: Be mindful of where you declare your variables. A variable declared inside a function can only be accessed within that function, whereas a variable declared outside a function can be accessed globally throughout your code (unless blocked by a function/ block scope).

  6. Immutability: Remember that const doesn't make the entire variable immutable, just the assignment. For example, if a const variable is assigned to an array or an object, the contents of the array or object can still be modified.


Callback Functions


What is it?

A callback function in JavaScript is a function that is passed into another function as an argument and is expected to be executed at a later time.

Where is it used?

Callback functions are used in a variety of circumstances in JavaScript, such as in event handlers, timers, and asynchronous operations (like reading files or making HTTP requests). They're a fundamental part of the JavaScript language and its asynchronous non-blocking I/O model.

How is it used?

  1. Definition: Define a callback function. This is a function you intend to execute after another function finishes.

  2. Passing as an Argument: Pass the callback function into the function where it will be invoked.

  3. Invocation: Inside the receiving function, the callback function is invoked (or called) when appropriate, usually after some operation has completed.

Code snippet to explain the same

Here's a simple example:

function greeting(name) {
   alert('Hello ' + name);
}

function processUserInput(callback) {
   let name = prompt('Please enter your name.');
   callback(name);
}

processUserInput(greeting);



In this example, the greeting function is a callback function. It's passed as an argument to processUserInput, and called inside processUserInput.

Takeaways / best practices

  1. Synchronous vs Asynchronous: Understand that callbacks can be used both synchronously and asynchronously. It's important to know the difference and to use the right type for the right task.

  2. Callback Hell: Be aware of and avoid "callback hell" or the "pyramid of doom" where callbacks are nested within callbacks, leading to code that's difficult to read and understand. This problem can be addressed by using promises or async/await syntax.

Pure Functions


What is it? 

A pure function in JavaScript is a function that always produces the same output for the same set of inputs and doesn't cause any side effects.

Where is it used?

Pure functions are used in array methods like mapfilter, and reduce, where the callback should be a pure function. They're also foundational in libraries like React (for components) and Redux (for reducers), and useful in unit testing because their output is predictable

How is it used?

  1. Deterministic Output: Create a function that for a given set of inputs, always returns the same output.

  2. Avoid Side Effects: Ensure the function doesn't change anything outside its own scope. This means it doesn't change input arguments, doesn't interact with external variables or states, doesn't make API calls, and doesn't change the console or the file system.

Code snippet to explain the same

Here's a simple example:

function sum(a, b) {
   return a + b;
}


The sum function is pure because for any given a and b, it will always return the same result and doesn't interact with any variables or states outside its scope.

Takeaways / best practices

  1. Predictability: Strive to make your functions pure where possible, because pure functions are predictable and make your code easier to read, debug, and test.

  2. Immutability: Avoid mutating input arguments inside a function. If you need to manipulate an object or an array, create a copy, manipulate that, and then return it.

Anonymous Functions


What is it? 

An anonymous function in JavaScript is a function that is defined without a name.

Where is it used? 

Anonymous functions are frequently used where functions are used as parameters, like in event handlers, setTimeout or setInterval calls, array methods like mapfilter, and reduce, or anywhere a function is needed for a short period and does not need to be called again elsewhere.

How is it used? 

  1. Definition: Anonymous functions can be declared using function declaration syntax without a name or more commonly using function expression syntax.

  2. Usage: After the anonymous function is defined, it can be used immediately (as in an Immediately Invoked Function Expression or IIFE), or it can be passed as an argument to another function to be executed later.

Code snippet to explain the same

Here's a simple example:

let arr = [1, 2, 3, 4];

// Using an anonymous function with the 'map' method
let squares = arr.map(function(item) {
    return item * item;
});

console.log(squares); // [1, 4, 9, 16]



In this example, the function passed to the map method is an anonymous function.

Takeaways / best practices 

  1. Short-lived Usage: Anonymous functions are great for one-time use cases where the function is not going to be reused.

  2. Debugging: Anonymous functions can make debugging a bit harder since they won't have a name to display in the stack trace. If a function is complex and might be hard to debug, consider named functions instead.

  3. Arrow Functions: You can use ES6 arrow functions to create anonymous functions with a more concise syntax.

  4. IIFEs: Anonymous functions are often used to create Immediately Invoked Function Expressions (IIFEs) which can be used to create private variable scopes and avoid global namespace pollution.


null and Undefined Values


What is it? 

In JavaScript, null and undefined are special values that represent the absence of a value or the absence of a defined value, respectively.

Where is it used?

Undefined typically occurs when a variable has been declared but not assigned a value. null, on the other hand, is used when you want to explicitly indicate or assign no value to a variable.

How is it used? 

  1. Undefined: When a variable is declared but not assigned, it defaults to undefined.

let test;
console.log(test); // undefined


  1. Null: You can assign null to a variable to signify that it doesn't have a value.

let test = null;
console.log(test); // null


Takeaways / best practices 

  1. Explicit Null: Use null when you want to explicitly indicate that a variable should have no value. This is more explicit than undefined, which could mean the variable simply hasn't been assigned yet.

  2. Checking for Either: If you want to check whether a value is either null or undefined, you can use the == null check. If you need to differentiate between null and undefined, use === instead.


== and === equality in Javascript


What is it? 

In JavaScript, == is the loose equality operator that compares two values for equality after performing any necessary type coercion, while === is the strict equality operator that compares two values for equality without type coercion.

Where is it used? 

Both operators are used widely in JavaScript for comparing values. The choice between loose and strict equality depends on whether you need type coercion in your comparisons.

How is it used?

  1. Loose Equality (==): If you're comparing two values and you want JavaScript to convert one type to another if necessary, use the loose equality operator (==).

console.log(1 == '1'); // true, because JavaScript converts '1' to a number before comparison


  1. Strict Equality (===): If you're comparing two values and you do not want type coercion, use the strict equality operator (===).

console.log(1 === '1'); // false, because '1' is a string and 1 is a number



Takeaways / best practices

  1. Prefer Strict Equality: As a best practice, you should generally use the strict equality (===) operator. It's safer because it doesn't result in unexpected truthy values due to type coercion.

  2. Understanding Loose Equality: If you choose to use loose equality (==), make sure you understand how JavaScript performs type coercion. This can often lead to counter-intuitive results.

  3. Comparing to Null or Undefined: When comparing to null or undefined, note that null == undefined is true, but null === undefined is false. So, == null is a convenient way to check if a value is either null or undefined.


Rest and Spread Operators in Javascript


What is it? 

In JavaScript, the rest operator (...) is used to gather remaining elements into an array, while the spread operator (...) is used to split up an array or object into individual elements or properties.

Where is it used?

Rest and spread operators are used widely in JavaScript for working with arrays and objects, function parameters, destructuring, copying, concatenating, merging, and more.

How is it used?

  1. Rest Operator: The rest operator can be used in function parameters or in destructuring assignments to gather remaining elements.

// In function parameters
function test(...args) {
  console.log(args);
}

test(1, 2, 3); // [1, 2, 3]

// In destructuring
let [first, ...rest] = [1, 2, 3, 4];
console.log(rest); // [2, 3, 4]



  1. Spread Operator: The spread operator can be used to expand arrays or objects, making it useful for making copies, merging, and more.

// With arrays
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]

// With objects
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 };
console.log(obj2); // { a: 1, b: 2, c: 3 }



Takeaways / best practices

  1. Avoiding Mutations: The spread operator can be very handy for creating new arrays or objects based on existing ones without mutating the originals. This is a good practice, especially in functional programming.

  2. Function Arguments: The rest operator is great for working with functions that accept an arbitrary number of arguments.

  3. Readability: Rest and spread operators can make your code more readable and less prone to errors.

  4. Merge and Copy: Spread operators are extremely useful for merging objects or arrays, and for creating shallow copies.

  5. Avoid Deep Nesting with Spread: While the spread operator creates a new array or object, be aware that it makes a shallow copy, which means nested objects or arrays will still be references and not new instances. For deep cloning, you'll need a different approach.


localStorage and sessionStorage


What is it? 

localStorage and sessionStorage are web storage objects in JavaScript that allow you to store data persistently (in localStorage) or for the duration of the session (in sessionStorage) on the user's browser.

Where is it used?

These storage objects are typically used when you want to preserve some form of state across sessions (for localStorage) or within a single session (for sessionStorage). They can be used for purposes like saving user preferences, caching data from a server, or tracking user activity during a session.

How is it used?

  1. Setting an Item: Use the setItem method to store data. This method accepts a key and a value, both of which should be strings.

localStorage.setItem('name', 'John');
sessionStorage.setItem('name', 'John');
  1. Getting an Item: Use the getItem method with a key to retrieve data.

let nameFromLocalStorage = localStorage.getItem('name');
let nameFromSessionStorage = sessionStorage.getItem('name');


  1. Removing an Item: Use the removeItem method with a key to remove data.

localStorage.removeItem('name');
sessionStorage.removeItem('name');
  1. Clearing All Data: Use the clear method to remove all data.

localStorage.clear();
sessionStorage.clear();



Takeaways / best practices

  1. Understanding Persistence: Understand the difference in persistence between localStorage (persists even when the browser is closed and reopened) and sessionStorage (only persists as long as the tab or window is open).

  2. String Data: Both localStorage and sessionStorage can only store strings. If you need to store complex objects, you'll need to stringify them before storage and parse them after retrieval.

  3. Size Limitations: Each has a limit (typically 5MB). Be aware of this if you plan to store a large amount of data.

  4. Security: Be careful not to store sensitive data unencrypted in localStorage or sessionStorage because it's accessible to any script on your page.

  5. Cross-Origin Isolation: Storage is isolated to the same-origin policy, meaning that pages from different domains cannot access the same storage.

  6. Avoid Overusing: Avoid overusing these web storage options. Not only because of size limitations, but also due to concerns around user privacy. Users have control over these storage APIs and can clear the storage if they choose.


Closures


What is it? 

A closure in JavaScript is a function that has access to its own scope, the outer function's scope, and the global scope, even after the outer function has closed.

Where is it used? 

Closures are used in JavaScript for data privacy, in event handlers and callbacks, in module patterns, and for currying and other functional programming patterns.

How is it used?

  1. Creating a Closure: Define a function inside another function. The inner function is the closure and has access to variables in the outer function's scope, even after the outer function completes.

function outerFunction(outerVariable) {
  return function innerFunction(innerVariable) {
    console.log('outerVariable:', outerVariable);
    console.log('innerVariable:', innerVariable);
  }
}

const newFunction = outerFunction('outside');
newFunction('inside'); // logs: "outerVariable: outside" and "innerVariable: inside"

Takeaways / best practices

  1. Memory Leaks: Be careful with closures as they can lead to memory leaks if not handled properly. For instance, if you reference an object from within a closure, it can't be garbage collected as long as the closure might be invoked in the future.

  2. Event Handlers and Callbacks: Closures are also used frequently in event handlers and callbacks, where they provide access to outer scope variables at the time the event is handled or the callback is called.


Hoisting


What is it?

Hoisting in JavaScript is a behavior where variable and function declarations are moved, or "hoisted", to the top of their containing scope during the compile phase before the code has been executed.

Where is it used?

Hoisting is a default behavior of JavaScript and happens every time the code is compiled and executed. Understanding hoisting helps avoid bugs related to order-dependent code.

How is it used?

  1. Variable Hoisting: JavaScript variables (declared with var) are hoisted to the top of their local scope (if declared inside a function) or to the top of the global scope (if declared outside of a function), but they're initialized with a value of undefined. Therefore, using a variable before it's declared results in a value of undefined.

console.log(myVar); // undefined
var myVar = 5;
  1. Function Hoisting: Function declarations (not function expressions or arrow functions) are hoisted to the top of their scope, and this includes the function body.

console.log(myFunc()); // "Hello"
function myFunc() {
  return "Hello";
}


Takeaways / best practices

  1. Declaration and Initialization: Understand that only declarations are hoisted, not initializations. If a variable is declared and initialized after using it, the variable exists but its value is undefined until initialization.

  2. let and const Hoisting: Variables declared with let and const are also hoisted to the top of their block, but they aren't initialized until their definition is evaluated. Accessing them before the declaration results in a ReferenceError.

  3. Function Hoisting: While function declarations are hoisted, function expressions (including those involving arrow functions) are not. Therefore, function expressions should be defined before they are used.

  4. Best Practices: As a best practice, always declare variables at the beginning of the scope. That way, the behavior of hoisting won't cause unexpected results in your code.


Currying


What is it?

Currying is a technique in JavaScript where a function with multiple arguments is transformed into a sequence of functions, each with a single argument.

Where is it used?

Currying is used in JavaScript in functional programming scenarios, particularly when you want to create a new function by fixing some of the arguments of the original function. It is also often used in event handlers and callback functions in JavaScript.

How is it used?

  1. Currying a Function: This involves transforming a multi-argument function into a sequence of single-argument functions.

function multiply(a, b, c) {
    return a * b * c;
}

function curryMultiply(a) {
    return function(b) {
        return function(c) {
            return multiply(a, b, c);
        }
    }
}

const curried = curryMultiply(2)(3)(4); // Returns 24



Takeaways / best practices

  1. Understanding Currying: Understanding currying can help you improve your functional programming skills. It's a technique often used in combination with other functional programming concepts, such as map, filter, and reduce.

  2. Partial Application: Currying can be used to create a function with preset parameters. This is known as partial application. The curried function can be more specific and thus easier to read and understand.


Event bubbling


What is it?

Event bubbling in JavaScript is a type of event propagation where the event starts from the deepest, or innermost, element in the DOM and then triggers handlers for that element and its ancestors (parent elements), going upward to the root of the tree.

Where is it used?

Event bubbling is used in scenarios where you want an event to be handled by an element as well as its ancestors. This is particularly useful for handling events on complex, nested structures like menus or lists.

How is it used?

  1. Understanding Event Bubbling: When an event (like a click) happens on an element in the DOM, that event is first dispatched to the element. If the element has an event handler for that type of event, the handler is triggered. Then, the event bubbles up to the parent element, and if it has an event handler for that event, it gets triggered. This continues up through the ancestors of the element.

Code snippet to explain the same

In this HTML structure:

<div id="parent">
  Parent
  <div id="child">
    Child
  </div>
</div>

With this JavaScript:

document.querySelector('#child').addEventListener('click', () => {
  console.log('Child clicked');
});

document.querySelector('#parent').addEventListener('click', () => {
  console.log('Parent clicked due to bubbling');
});


Clicking on the "Child" div outputs:

Child clicked
Parent clicked due to bubbling



Takeaways / best practices

  1. Understanding Event Bubbling: Understanding event bubbling is key to properly managing events in complex DOM structures. Knowing how events propagate can help you understand why certain handlers are triggered.

  2. Event.stopPropagation(): You can stop bubbling by calling event.stopPropagation() in the event handler. However, this should be used sparingly, as it can make your event handling code harder to debug and understand.

  3. Delegate Events: You can take advantage of event bubbling to add an event listener to a parent element that will handle events for its child elements. This is known as event delegation, and it can make your code more efficient and easier to manage, particularly for dynamic elements.

  4. Capturing Phase vs Bubbling Phase: Event propagation in the DOM actually involves two phases: capturing (event goes down the tree) and bubbling (event comes up the tree). By default, event handlers are executed in the bubbling phase, but you can set handlers to run in the capturing phase by setting the third argument of addEventListener to true. This is rarely used, but it's important to be aware of it.


Event Loops


What is it? 

The Event Loop in JavaScript is a process that monitors the Call Stack and the Callback Queue, and pushes any function from the Callback Queue to the Call Stack when the Call Stack is empty, facilitating asynchronous behavior in JavaScript, a single-threaded language.

Where is it used?

The Event Loop is inherent to how JavaScript operates and is crucial to asynchronous operations like AJAX calls, setTimeout, setImmediate, process.nextTick, and Promises.

How is it used? 

  1. Understanding the Components: The JavaScript runtime uses a Call Stack for function execution, a Web API provided by the browser for asynchronous operations, and a Callback Queue for pushing the callbacks from Web API when they're done.

  2. Call Stack: JavaScript is single-threaded and can only do one thing at a time. When a function is called, it's added to the top of the Call Stack. When it's finished, it's removed from the stack.

  3. Web API and Callback Queue: When JavaScript encounters an asynchronous operation like a setTimeout, it uses the browser's Web API to handle it. When the operation is done, its callback function is pushed to the Callback Queue.

  4. Event Loop: The Event Loop continuously checks if the Call Stack is empty. If it is, it takes the first function from the Callback Queue and pushes it to the Call Stack to be executed.

Takeaways / best practices

  1. Asynchronous Behavior: The Event Loop is what allows JavaScript to perform non-blocking operations, even though JavaScript is single-threaded. It's fundamental to understanding asynchronous JavaScript.

  2. Microtask Queue: Promise callbacks are placed in a separate queue, the Microtask Queue, which has a higher priority than the Callback Queue. The Event Loop will process all the callbacks in the Microtask Queue before it processes callbacks in the Callback Queue.

  3. Blocking the Event Loop: Since JavaScript is single-threaded, long-running functions can block the Event Loop and freeze the page. You should aim to keep your JavaScript functions short and non-blocking.

  4. Zero DelayssetTimeout with a delay of 0 (or setImmediate and process.nextTick in Node.js) doesn't actually run the callback immediately. It runs it after all the functions in the Call Stack have finished and all the microtasks have been processed.

Debouncing and throttling in Javascript


What is it?

Debouncing and throttling are techniques in JavaScript to control how often a function can run, useful for improving performance and handling user interactions more smoothly.

Where is it used?

These techniques are commonly used in cases where an event is triggered frequently, such as user typing in an input field (debounce), or a user scrolling a webpage (throttle).

How is it used?

  1. Debounce: This technique involves delaying a function until after a set amount of time has passed since it was last called.


function debounce(func, delay) {
  let timeoutId;
  return function() {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(func, delay);
  };
}

// Using debounce: Log the input field's value 500ms after the user stops typing
let inputField = document.querySelector('input');
inputField.addEventListener('keyup', debounce(() => console.log(inputField.value), 500));
  1. Throttle: Throttling involves ensuring a function is not called more than once every set amount of time.

function throttle(func, delay) {
  let lastExecutionTime = 0;
  return function() {
    const now = Date.now();
    if (now - lastExecutionTime > delay) {
      lastExecutionTime = now;
      func();
    }
  };
}


// Using throttle: Log the scroll position at most once every 2000ms

window.addEventListener('scroll', throttle(() => console.log(window.scrollY), 2000));



Takeaways / best practices

  1. Use Appropriately: Use debounce when you want to limit calls to a function until an event has stopped being fired for a duration. Use throttle when you want to ensure an event doesn't fire more than once every X milliseconds.

  2. Performance: Debouncing and throttling can greatly improve the performance of your website by limiting the frequency of function calls.

  3. Use Libraries: Libraries such as lodash offer debounce and throttle functions, which can simplify your code if you're already using these libraries in your project.


Array Slicing and Splicing


What is it? 

Array slicing and splicing are methods in JavaScript to manipulate arrays - 'slice' is used to extract a section of an array and return a new array, whereas 'splice' is used to modify the original array by adding, replacing, or removing elements.

Where is it used?

These methods are commonly used in data manipulation, where we need to manipulate arrays based on our requirements, like extracting a specific portion of data or modifying the original data array.

How is it used?

  1. Slice: The slice method extracts a section of an array and returns a new array without modifying the original one. It takes two arguments: the start index (inclusive), and the end index (exclusive). If no end index is provided, it slices till the end of the array.

let arr = [1, 2, 3, 4, 5];
let slicedArr = arr.slice(1, 3);
console.log(slicedArr); // Outputs: [2, 3]
console.log(arr); // Outputs: [1, 2, 3, 4, 5] (original array is not modified)


  1. Splice: The splice method changes the original array by adding, replacing, or removing elements. It takes at least two arguments: the start index, and the number of items to remove. Any additional arguments will be added to the array at the start index.

let arr = [1, 2, 3, 4, 5];
let splicedArr = arr.splice(1, 2, "two", "three");
console.log(splicedArr); // Outputs: [2, 3] (removed elements)
console.log(arr); // Outputs: [1, "two", "three", 4, 5] (original array is modified)


Takeaways / best practices

  1. Choosing Between Slice and Splice: If you want to manipulate an array without affecting the original one, use slice. If you need to add, replace, or remove elements from the original array, use splice.

  2. Start and End Index: Remember that in both methods, the start index is inclusive, while the end index is exclusive. Also, if these indices are negative, they are counted from the end of the array.

  3. Understanding Returned Values: Keep in mind that slice returns the new array of extracted elements, while splice returns an array of removed elements.


Pass by Value and Pass by Reference


What is it? 

In JavaScript, passing by value means that an actual value is passed to the function, while passing by reference means an address (reference) of the object in memory is passed to the function.

Where is it used?

This concept is used when we pass arguments to a function. Primitive types (numbers, strings, booleans, null, undefined, and symbols) are passed by value, while non-primitive types (objects, arrays, and functions) are passed by reference.

How is it used?


  1. Pass by Value: When a primitive value is passed to a function, JavaScript creates a copy of the value and passes it. Changes to the argument inside the function do not affect the original variable.

let num = 10;

function changeValue(n) {
  n = 20;
}

changeValue(num);
console.log(num); // Outputs: 10 (original value is unchanged)


  1. Pass by Reference: When a non-primitive value (object or array) is passed, JavaScript passes a reference to the object. Changes to the argument inside the function will affect the original object.

let obj = { val: 10 };

function changeValue(object) {
  object.val = 20;
}

changeValue(obj);
console.log(obj.val); // Outputs: 20 (original object is modified)



Takeaways / best practices

  1. Understanding Differences: It's important to understand the difference between pass by value and pass by reference as it can lead to unexpected results if you assume JavaScript behaves like other programming languages.

  2. Avoid Mutations: Since changes to objects and arrays inside a function will modify the original, it's often a good practice to avoid mutating these objects if you want to prevent side-effects. You can create copies of objects or arrays and work with them instead.

  3. Use of Constants: If you define your objects and arrays as constants (const), JavaScript will prevent you from assigning them to a new value, but the contents of the object or array can still be modified due to pass by reference.


Promise Chaining



What is it?

Promise chaining in JavaScript is a technique where the result of a promise is passed from one .then() method call to another, allowing for the sequential execution of asynchronous operations.

Where is it used?

This technique is used when you have multiple asynchronous operations that need to be executed in a specific order, with each operation depending on the result of the previous one.


How is it used?

  1. A promise is an object that represents a future value. It has a .then() method that you can call with a callback function. This function will be executed when the promise resolves.

  2. The callback function passed to .then() can return a new value or a new promise. If it returns a value, the next .then() in the chain will be called with that value. If it returns a promise, the next .then() will wait until that promise resolves.


let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000); // after 1 sec, the promise will resolve with value 1
});

promise
.then(result => {
  console.log(result); // 1
  return result * 2;
})
.then(result => {
  console.log(result); // 2
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000); // after 1 sec, this promise will resolve with value 4
  });
})
.then(result => {
  console.log(result); // 4
});


Takeaways / best practices

  1. Handling Errors: If an error is thrown in one of the callbacks, the promise will be rejected and further .then() calls will be skipped until a .catch() is encountered.

  2. Avoid Nesting: Promise chaining helps to avoid the "callback hell" or "Pyramid of Doom" problem of deeply nested callbacks, making the code more readable.

  3. Always Return: Always remember to return results (either values or promises) from your .then() callbacks, otherwise the subsequent .then() calls will receive undefined.

  4. Use async/await: For even cleaner and more readable code, consider using async/await syntax which is syntactic sugar over promise chaining.


Type Coercion


What is it?

Type coercion in JavaScript is the automatic or implicit conversion of values from one data type to another (such as string to a number).

Where is it used?

Type coercion occurs when operators are applied to values of different types, or when system objects require values to be of a certain type. It's a fundamental concept in JavaScript and can be used intentionally to convert types, but can also cause unexpected behaviors if not understood properly.

How is it used?

Type coercion often occurs when using the equality operator == which does type coercion to compare two different types.

console.log(1 == '1'); // true, because '1' is coerced to a number

Coercion can also occur when performing arithmetic operations:

console.log('5' - 1); // 4, because '5' is coerced to a number
console.log('5' + 1); // 51, because 1 is coerced to a string

Logical expressions can also cause type coercion:

console.log(true == 1); // true, because true is coerced to a number (1)



Takeaways / best practices

  1. Know the Difference Between == and ===== allows type coercion while === doesn't. Using === can help prevent bugs due to unexpected type coercions.

  2. Explicit Over Implicit: While JavaScript will perform type coercion automatically when needed, it's generally a good practice to manually convert (or "coerce") values to the expected type. This makes your code more readable and less prone to bugs.

  3. Understand Falsy Values: In logical operations, JavaScript coerces values to booleans. It's important to understand which values are "falsy" and get coerced to false (like 0''NaNnullundefined), and which are "truthy" and get coerced to true.

  4. Avoid Mixing Types: If possible, try to avoid operations between different types to sidestep the whole issue of type coercion.


Memoization


What is it?

Memoization in JavaScript is a performance optimization technique that involves storing the results of expensive function calls and reusing those cached results when the same inputs are provided.

Where is it used?

Memoization is primarily used in situations where the same heavy or expensive computation (like an API call or a complex calculation) is done multiple times with the same inputs. It can be especially helpful in improving the efficiency of recursive algorithms by avoiding repetitive computations.

How is it used?

  1. Create a storage (usually an object) to store the results of function calls.

  2. Inside the function, check if the result for the current input is already present in the storage.

  3. If it's present, return the stored result instead of running the computation again.

  4. If it's not present, run the computation, store the result in the storage, and then return the result.

Here's an example with a simple recursive function for calculating Fibonacci numbers:

const fib = (function() {
  let memo = {};

  return function(n) {
    if (memo[n]) {
      return memo[n];
    } else if (n <= 2) {
      return 1;
    } else {
      memo[n] = fib(n - 1) + fib(n - 2);
      return memo[n];
    }
  };
})();
console.log(fib(50)); // Executes quickly even with a large number.



Takeaways / best practices

  1. When to Use: Use memoization when you have functions that are computationally expensive and are called repeatedly with the same inputs.

  2. Memory Considerations: Memoization improves performance at the cost of higher memory usage. Therefore, it's important to strike a balance between speed and memory usage.

Destructuring


What is it?

Destructuring in JavaScript is a syntax that allows you to unpack values from arrays, or properties from objects, into distinct variables.

Where is it used?

Destructuring is used whenever you want to extract values from an array or properties from an object to variables, reducing the amount of code you need to write and increasing readability. It's frequently used when importing modules, working with function arguments, or handling response objects from APIs.

How is it used?

  • Array Destructuring:

    1. Declare the variables that will store the values on the left-hand side of the assignment operator, enclosed in brackets.

    2. Set those equal to the array from which you want to extract the values.

  • Object Destructuring:

    1. Declare the variables that will store the values on the left-hand side of the assignment operator, enclosed in curly braces.

    2. Set those equal to the object from which you want to extract the values.

// Array destructuring
let [first, second, , fourth] = ["Apple", "Banana", "Grape", "Orange"];
console.log(first); // Apple
console.log(fourth); // Orange

// Object destructuring
let { name, age } = { name: "Alice", age: 25, job: "Engineer" };
console.log(name); // Alice
console.log(age); // 25



Takeaways / best practices

  1. Skipping values in arrays: You can skip values in array destructuring by leaving gaps in the destructuring assignment.

  2. Default values: Destructuring allows you to assign default values to the variables, in case the value extracted is undefined.

  3. Nested destructuring: Destructuring can be used for nested arrays and objects, too. But be careful as this can make the code hard to read if overused.

  4. Function parameters: Destructuring can make handling function parameters more readable and flexible, especially when dealing with object parameters.


Strict Mode


What is it?

Strict Mode is a feature in JavaScript that enforces stricter parsing and error handling at runtime, preventing or throwing errors for certain problematic operations that are otherwise allowed in non-strict mode.

Where is it used?

It's used during the development process when a programmer wants to enforce better practices and catch common coding mistakes. Strict mode helps in avoiding silent errors and making the code more robust, understandable, and easier to manage.

How is it used?

  1. To enable strict mode for an entire script, place the string "use strict"; at the very top of your JavaScript file or script tag.

  2. To enable strict mode for a specific function only, place the string "use strict"; at the beginning of the function body.

// Enabling strict mode for an entire script
"use strict";
x = 10; // ReferenceError: x is not defined

// Enabling strict mode for a function only
function strictFunction() {
  "use strict";
  y = 20; // ReferenceError: y is not defined
}
strictFunction();


Takeaways / best practices

  1. Catch silent errors: One of the biggest advantages of using strict mode is that it turns silent errors into thrown errors.

  2. Prevent accidental globals: In strict mode, if you accidentally assign a value to an undeclared variable, a read-only variable, or a read-only global variable, JavaScript will throw an error.

  3. Eliminate this coercion: In non-strict mode, this (when it's undefined or null) is coerced into the global object. Strict mode prevents this by ensuring that this is undefined in functions that are not methods or constructors.

  4. Prohibits duplicate parameter names: In strict mode, JavaScript will throw an error when it encounters a function that has two or more parameters with the same name.

  5. Adopt strict mode gradually: If you're working with a large, legacy codebase that wasn't designed with strict mode in mind, consider applying strict mode to individual functions or sections of your code, rather than the whole script at once.

  6. Use in modern JavaScript development: As the modern JavaScript syntaxes (like ES6 classes and modules) are automatically in strict mode, adopting these syntaxes can help ensure you're writing better, more predictable code.






Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article