JavaScript In-Depth
JavaScript In-Depth

JavaScript In-Depth

Updated
Apr 30, 2023 02:22 PM
Category
Programing
Status
Open

Var Declaration


When you declare a variable with var, the variable is hoisted to the top of its scope, which means it can be accessed and used before it is declared. This is known as variable hoisting. For example:
//Output: undefined
console.log(x);
var x = 5;
In the example above, the variable x is declared and assigned a value of 5 on the second line. However, when we try to log x to the console on the first line, it outputs undefined instead of throwing an error.
Here is an example using the let declaration:
//ReferenceError: Cannot access 'x' before initialization
console.log(x);
let x = 5;
In the example above, the result of trying to access x is going to throw an error.
This is because the var x declaration is hoisted to the top of its scope, which in this case is the global scope.
Unlike let and const, var variables have function scope, which means they are only accessible within the function in which they are declared. If a var variable is declared outside of any function, it has global scope and can be accessed from anywhere in the code.
One thing to be aware of when using var variables is that they can be redeclared within the same scope without throwing an error. For example:
var x = 5;
var x = 10;
console.log(x); // Output: 10
In the example above, the variable x is declared and assigned a value of 5. Then, on the second line, x is declared again and assigned a new value of 10. The console.log(x) statement on the third line outputs 10, because x now holds that value.

Differences between var and let

Feature
var
let
Scope
Function scope
Block scope
Hoisting
Yes, variable declarations are hoisted
No, variable declarations are not hoisted
Initialization
Initialized with undefined when hoisted
Not initialized, using the variable before declaration causes a ReferenceError
Global Object
When declared globally, attached to the global object (window in browsers)
When declared globally, not attached to the global object
Re-declaration
Allows re-declaration of the same variable within the same scope
Disallows re-declaration of the same variable within the same scope
By using let instead of var, you can create block-scoped variables, avoid hoisting-related issues, and prevent accidentally re-declaring variables, resulting in more predictable and maintainable code.
Overall, var variables are still used in many existing codebases, but they have some limitations and quirks that can make them less preferable than let and const variables in modern JavaScript.

Advance Operators


Ternary Operator

The ternary operator (also called the conditional operator) is a shorthand way of writing an if...else statement. It has the following syntax:
condition ? expressionIfTrue : expressionIfFalse
let age = 18;
let isAdult = age >= 18 ? "Yes" : "No";

console.log(isAdult); // "Yes"
In this example, since the condition age >= 18 is true, the isAdult variable is assigned the value "Yes". If the condition were false, it would be assigned the value "No".

Typeof Operator

The typeof operator returns a string indicating the data type of a given value:
console.log(typeof 42);           // "number"
console.log(typeof "Hello");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof {name: "John"}); // "object"

Nullish Coalescing Operator

The nullish coalescing operator (??) returns the right-hand side operand when the left-hand side operand is null or undefined. Otherwise, it returns the left-hand side operand:
let a = null;
let b = "Hello, World!";
let c = a ?? b;

console.log(c); // "Hello, World!"

Optional Chaining Operator

The optional chaining operator (?.) allows you to access properties of an object without throwing an error if the object is null or undefined:
let person = {
  name: "John",
  address: {
    street: "Main St",
    city: "New York",
  },
};

console.log(person.address?.street); // "Main St"
console.log(person.job?.title);      // undefined (no error)
In this example, since person.job is not defined, using the optional chaining operator with person.job?.title returns undefined without throwing an error. If we had used person.job.title without the optional chaining operator, a TypeError would have occurred.

Instanceof Operator

The instanceof operator is used to check if an object is an instance of a particular class or constructor:
class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
}

let myCar = new Car("Toyota", "Camry");

console.log(myCar instanceof Car); // true
console.log(myCar instanceof Object); // true
In this example, myCar is an instance of both the Car class and the Object class, so both checks return true.

In Operator

The in operator is used to check if an object has a specified property:
let person = {
  name: "John",
  age: 30,
};

console.log("name" in person); // true
console.log("address" in person); // false
In this example, the person object has a name property, so ("name" in person) returns true. It does not have an address property, so ("address" in person) returns false.

Spread Operator

The spread operator (...) is used to expand elements of an iterable object (like arrays or strings) into individual elements. It can be particularly useful in copying arrays, concatenating arrays, and passing elements of an array as function arguments:

Copying Arrays

let originalArray = [1, 2, 3];
let copiedArray = [...originalArray];

console.log(copiedArray); // [1, 2, 3]

Concatenating Arrays

let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
let concatenatedArray = [...array1, ...array2];

console.log(concatenatedArray); // [1, 2, 3, 4, 5, 6]

Passing Array Elements as Function Arguments

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

let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

Rest Operator

The rest operator (...) can also be used in a different context, to collect the remaining elements of an iterable into an array. This is particularly useful when working with functions that accept a variable number of arguments:
function sum(...numbers) {
  let total = 0;
  for (let number of numbers) {
    total += number;
  }
  return total;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
In this example, the sum function can accept any number of arguments, which are then collected into the numbers array using the rest operator.

The Global Object in JavaScript


The global object in JavaScript is a top-level container for variables and functions that are accessible throughout the entire application. It provides built-in properties and functions and serves as a container for user-defined global variables and functions.

Key Features

  1. Built-in properties and functions: The global object includes essential built-in properties and functions, such as constructors (e.g., Object, Array, Date), global functions (e.g., parseInt(), parseFloat(), isNaN()), and global properties (e.g., undefined, NaN, Infinity).
  1. User-defined global variables and functions: When you declare a global variable or function, it becomes a property of the global object. However, this does not apply to module systems (e.g., ECMAScript modules or CommonJS).98
  1. Access to global variables and functions: The global object allows you to access global variables and functions from any part of your code, including within functions.
  1. this value in the global scope: In the global scope, the this keyword refers to the global object (window in browsers and global in Node.js). In ECMAScript modules, this is undefined in the global scope.

Environments

  • Browser: In a browser environment, the global object is the window object.
  • Node.js: In Node.js, the global object is the global object.

Best Practices

  • Limit the use of global variables and functions to avoid code that is harder to maintain, test, and debug.
  • Rely on local variables, function parameters, and modules to share data and functionality.

Example

// Built-in property
console.log(Math.PI); // Output: 3.141592653589793

// User-defined global variable
var globalVar = 'I am global!';
console.log(window.globalVar); // Output: 'I am global!' (in a browser environment)
By understanding the global object, you can take advantage of built-in properties and functions, as well as manage your global variables and functions effectively.
notion image

Additional Considerations

When working with the global object in JavaScript, it is essential to consider the following aspects to ensure your code is clean, maintainable, and efficient:

Avoid Polluting the Global Namespace

Adding too many global variables or functions can lead to naming conflicts and make your code harder to understand and maintain. To avoid polluting the global namespace:
  • Declare variables and functions within the smallest possible scope.
  • Use closures, objects, or classes to encapsulate related functionality.
  • Utilize module systems like ECMAScript modules (ESM) or CommonJS to keep variables and functions local to a specific module.

Use Global Object as a Fallback

Sometimes you may need to use global variables or functions as a fallback when a locally scoped variable is not available. In such cases, you can use the global object to check for the existence of a variable or function before creating it.
// Check if a global variable exists, if not, create it
if (!window.myGlobalVar) {
  window.myGlobalVar = 'I am a global fallback!';
}

Be Aware of Platform Differences

While the global object is window in browser environments and global in Node.js environments, other JavaScript environments might use different global objects. Be aware of the platform you are working on and adjust your code accordingly.

Use Strict Mode

Strict mode is a feature in JavaScript that helps you write safer and more robust code by enforcing stricter rules and disabling some error-prone features. When using strict mode, the global object behaves differently:
  • Assigning a value to an undeclared variable will throw a ReferenceError, preventing the accidental creation of global variables.
  • The value of this in functions not called as methods (i.e., "free" functions) will be undefined instead of the global object.
To enable strict mode, add the following line at the beginning of your script or function:
'use strict';
By following these additional considerations, you can work with the global object more effectively and ensure your JavaScript code is cleaner, more maintainable, and less prone to errors.

JavaScript Hoisting

Hoisting is a concept in JavaScript where variable and function declarations are moved to the top of their containing scope during the compilation phase, before the code is executed. This can lead to some unexpected behavior, especially for developers who are new to JavaScript.

How Hoisting Works

In JavaScript, there are two main types of declarations: var and function. When the JavaScript engine compiles your code, it moves these declarations to the top of their scope. This is known as hoisting. Let's look at an example:
console.log(message);
var message = "Hello, World!";
You might expect this code to output undefined, but because of hoisting, it actually works like this:
var message;
console.log(message);
message = "Hello, World!";
The var message declaration is hoisted to the top of the scope, while the assignment remains in place. This is why the output is undefined and not an error.

Function Hoisting

Function declarations are also hoisted in JavaScript. Here's an example:
console.log(greet("John"));

function greet(name) {
  return "Hello, " + name + "!";
}
The function declaration is hoisted to the top of the scope, so the code is effectively rearranged as follows:
function greet(name) {
  return "Hello, " + name + "!";
}

console.log(greet("John"));
The output in this case is "Hello, John!".

Hoisting with let and const

In contrast to var, variable declarations using let and const are not hoisted. They still get processed during the compilation phase, but they are not initialized until their actual line in the code is reached. Accessing them before their declaration results in a ReferenceError.

How to Avoid Hoisting Issues

To minimize confusion caused by hoisting, follow these best practices:
  1. Declare variables and functions at the top of their scope.
  1. Use let and const instead of var for variable declarations.
  1. Avoid using variables or functions before they are declared.

Objects: In-Depth Explanation

JavaScript objects are a versatile and essential data structure used to store and manage data as key-value pairs. In this in-depth explanation, we will explore the different ways to create, manipulate, and access object information.

Creating Objects

There are several ways to create objects in JavaScript:

1. Object Literal

The object literal is the most common and straightforward way to create an object. It uses curly braces ({}) and a comma-separated list of key-value pairs:
let person = {
  name: "John",
  age: 30,
  greet: function() {
    console.log("Hello, my name is " + this.name);
  }
};

2. Object Constructor

You can create an object using the Object() constructor:
let person = new Object();
person.name = "John";
person.age = 30;
person.greet = function() {
  console.log("Hello, my name is " + this.name);
};

3. Constructor Function

A constructor function is a custom function that creates and initializes objects:
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log("Hello, my name is " + this.name);
  };
}

let person = new Person("John", 30);

4. Object.create()

The Object.create() method allows you to create a new object with the specified prototype object:
let personPrototype = {
  greet: function() {
    console.log("Hello, my name is " + this.name);
  }
};

let person = Object.create(personPrototype);
person.name = "John";
person.age = 30;

Accessing Object Information

There are two primary ways to access object properties:

1. Dot Notation

Dot notation is the most common and straightforward way to access an object's properties:
console.log(person.name); // "John"

2. Bracket Notation

Bracket notation allows you to access an object's properties using a string, which can be a variable or an expression:
console.log(person["name"]); // "John"

let propertyName = "age";
console.log(person[propertyName]); // 30

let dynamicProperty = "na" + "me";
console.log(person[dynamicProperty]); // "John"

Enumerating Object Properties

You can iterate over an object's properties using loops:

1. For...in Loop

The for...in loop iterates over the enumerable properties of an object:
for (let key in person) {
  console.log(key + ": " + person[key]);
}

2. Object.keys() and Object.values()

The Object.keys() method returns an array of an object's property names, while the Object.values() method returns an array of the property values:
let keys = Object.keys(person);
let values = Object.values(person);

for (let i = 0; i < keys.length; i++) {
  console.log(keys[i] + ": " + values[i]);
}

3. Object.entries()

The Object.entries() method returns an array of an object's key-value pairs:
let entries = Object.entries(person);

for (let [key, value] of entries) {
  console.log(key + ": " + value);
}

Modifying Objects

You can modify an object by adding, updating, or deleting properties:
  1. Adding a property:
person.country = "USA";
  1. Updating a property:
person.age = 31;
  1. Deleting a property:
delete person.age;

Object Methods

Objects can have methods, which are functions associated with an object. You can define object methods using the function keyword or the shorthand method syntax:
let person = {
  name: "John",
  age: 30,
  greet: function() {
    console.log("Hello, my name is " + this.name);
  },
  // Shorthand method syntax
  celebrateBirthday() {
    this.age++;
  }
};

Object Prototypes and Inheritance

In JavaScript, objects can inherit properties and methods from other objects through a mechanism called prototypes. When you create an object, it automatically inherits properties and methods from its prototype, which is usually the constructor function's prototype property.
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

let person = new Person("John", 30);
person.greet(); // "Hello, my name is John"
In this example, the greet method is defined on the Person constructor's prototype, making it available to all objects created using the Person constructor.

Static and Instance Methods

A static method is a method that belongs to a class itself, rather than to an instance of the class. Static methods are defined on the class, and they can be called directly on the class without creating an instance of the class. They are often used to create utility functions or to perform operations that don't depend on the instance's state.
In JavaScript, there are two types of methods:
  1. Instance methods: These methods belong to an instance of a class and can be called on the instance. They usually have access to the instance's properties and are designed to perform operations specific to that instance. For example, in the case of an Array, methods like push(), pop(), and map() are instance methods because they operate on individual instances of arrays.
  1. Static methods: These methods belong to the class itself and can be called directly on the class without creating an instance. They don't have access to the instance's properties or methods and are designed to perform operations that don't depend on a specific instance's state. In JavaScript, static methods are defined using the static keyword within a class definition. For example, in the case of the Array class, methods like Array.from() and Array.isArray() are static methods because they do not operate on a specific array instance.
Here's an example of a class with both instance methods and a static method:
class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  // Instance method
  area() {
    return Math.PI * this.radius * this.radius;
  }

  // Instance method
  circumference() {
    return 2 * Math.PI * this.radius;
  }

  // Static method
  static areCirclesEqual(circle1, circle2) {
    return circle1.radius === circle2.radius;
  }
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.area()); // 78.53981633974483 (Instance method)
console.log(Circle.areCirclesEqual(circle1, circle2)); // false (Static method)
In the example above, area() and circumference() are instance methods, while areCirclesEqual() is a static method. You can see how they are called differently, with instance methods being called on an instance of the class and static methods being called directly on the class.

Arrays: In-Depth Explanation

Arrays are an essential data structure in JavaScript, used for storing and managing ordered collections of items. In this in-depth explanation, we will explore different ways to create, manipulate, and access array information.

Creating Arrays

There are several ways to create arrays in JavaScript:
  1. Array Literal The array literal is the most common and straightforward way to create an array. It uses square brackets ([]) and a comma-separated list of values:
let fruits = ["apple", "banana", "orange"];
  1. Array Constructor You can create an array using the Array() constructor:
let fruits = new Array("apple", "banana", "orange");
  1. Array.from() Method The Array.from() method creates a new array from an array-like or iterable object:
let nodeList = document.querySelectorAll("p");
let paragraphs = Array.from(nodeList);

Accessing Array Elements

Array elements are accessed using their index, which starts from 0:
console.log(fruits[0]); // "apple"
console.log(fruits[1]); // "banana"
console.log(fruits[2]); // "orange"

Manipulating Arrays

Arrays have numerous built-in methods to manipulate their elements:
  1. Adding elements:
      • push(): adds elements to the end of an array.
      • unshift(): adds elements to the beginning of an array.
fruits.push("grape");
fruits.unshift("strawberry");
  1. Removing elements:
      • pop(): removes the last element from an array.
      • shift(): removes the first element from an array.
fruits.pop();
fruits.shift();
  1. Reversing and sorting elements:
      • reverse(): reverses the order of elements in an array.
      • sort(): sorts the elements of an array.
fruits.reverse();
fruits.sort();
  1. Slicing and splicing elements:
      • slice(): returns a shallow copy of a portion of an array.
      • splice(): adds/removes elements from an array.
let slicedFruits = fruits.slice(1, 3);
fruits.splice(1, 0, "kiwi");
  1. Concatenating arrays:
      • concat(): merges two or more arrays into a new array.
let vegetables = ["carrot", "broccoli", "spinach"];
let combined = fruits.concat(vegetables);
  1. Iterating over elements:
      • forEach(): executes a provided function for each array element.
fruits.forEach(function(fruit, index) {
  console.log("Fruit " + index + ": " + fruit);
});
  1. Searching and filtering elements:
      • indexOf(): returns the first index at which a given element is found in the array.
      • includes(): determines whether an array includes a certain element.
      • find(): returns the first element in the array that satisfies a provided testing function.
      • filter(): creates a new array with all elements that pass the test implemented by a provided function.
console.log(fruits.indexOf("banana")); // 1
console.log(fruits.includes("orange")); // true

let foundFruit = fruits.find(fruit => fruit.startsWith("b"));
let filteredFruits = fruits.filter(fruit => fruit.length > 5);
  1. Transforming and reducing elements:
      • map(): creates a new array with the results of calling a provided function on every element in the array.
      • reduce(): applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
      let fruitLengths = fruits.map(fruit => fruit.length);
      let totalLength = fruits.reduce((accumulator, fruit) => accumulator + fruit.length, 0);

      Multidimensional Arrays

      Arrays can contain other arrays, creating multidimensional arrays. For example, you can create a two-dimensional array (matrix) like this:
      let matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
      ];
      To access elements in a multidimensional array, use multiple indices:
      console.log(matrix[0][0]); // 1
      console.log(matrix[1][2]); // 6
      console.log(matrix[2][1]); // 8

      Array Iteration

      There are several ways to iterate over an array:
    1. For Loop
    2. for (let i = 0; i < fruits.length; i++) {
        console.log(fruits[i]);
      }
    3. For...of Loop
    4. for (let fruit of fruits) {
        console.log(fruit);
      }
    5. forEach() Method
    6. fruits.forEach(fruit => console.log(fruit));

      Array Destructuring

      Array destructuring allows you to unpack values from an array and assign them to variables:
      let [firstFruit, secondFruit, ...remainingFruits] = fruits;
      
      console.log(firstFruit); // "apple"
      console.log(secondFruit); // "banana"
      console.log(remainingFruits); // ["orange"]
 

Object Methods

1. Object.assign()

The Object.assign() method is used to copy values of all enumerable own properties from one or more source objects to a target object.
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };

const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 3, c: 4 }

2. Object.create()

The Object.create() method creates a new object with the specified prototype object and properties.
const personProto = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

const person = Object.create(personProto, {
  firstName: { value: "John" },
  lastName: { value: "Doe" },
  age: { value: 30 }
});

3. Object.entries()

The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const entries = Object.entries(person);
console.log(entries); // [["firstName", "John"], ["lastName", "Doe"], ["age", 30]]

4. Object.freeze()

The Object.freeze() method freezes an object, preventing new properties from being added, existing properties from being removed, and existing properties from being modified.
const person = { firstName: "John", lastName: "Doe", age: 30 };
Object.freeze(person);

person.age = 31; // This will have no effect
console.log(person.age); // 30

5. Object.isFrozen()

The Object.isFrozen() method determines if an object is frozen.
const person = { firstName: "John", lastName: "Doe", age: 30 };
console.log(Object.isFrozen(person)); // false

Object.freeze(person);
console.log(Object.isFrozen(person)); // true

6. Object.keys()

The Object.keys() method returns an array of a given object's own enumerable property names.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const keys = Object.keys(person);
console.log(keys); // ["firstName", "lastName", "age"]

7. Object.values()

The Object.values() method returns an array of a given object's own enumerable property values.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const values = Object.values(person);
console.log(values); // ["John", "Doe", 30]

8. Object.hasOwnProperty()

The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as its own property.
const person = { firstName: "John", lastName: "Doe", age: 30 };
console.log(person.hasOwnProperty("firstName")); // true
console.log(person.hasOwnProperty("middleName")); // false

9. Object.is()

The Object.is() method determines whether two values are the same value.
console.log(Object.is(42, 42)); // true
console.log(Object.is("foo", "foo")); // true
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is([], [])); // false

10. Object.defineProperty()

The Object.defineProperty() method defines a new property directly on an object or modifies an existing property and returns the object.
const person = { firstName: "John", lastName: "Doe" };

Object.defineProperty(person, "fullName", {
  value: function() {
    return this.firstName + " " + this.lastName;
  },
  writable: false,
  enumerable: false,
  configurable: false
});

console.log(person.fullName()); // "John Doe"

11. Object.defineProperties()

The Object.defineProperties() method defines new or modifies existing properties directly on an object and returns the object.
const person = {};

Object.defineProperties(person, {
  firstName: { value: "John", writable: true, enumerable: true, configurable: true },
  lastName: { value: "Doe", writable: true, enumerable: true, configurable: true },
  age: { value: 30, writable: true, enumerable: true, configurable: true }
});

console.log(person.firstName); // "John"

12. Object.getOwnPropertyDescriptor()

The Object.getOwnPropertyDescriptor() method returns an object describing the configuration of a specific property on a given object.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const descriptor = Object.getOwnPropertyDescriptor(person, "firstName");

console.log(descriptor);
// {
//   value: "John",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

13. Object.getOwnPropertyDescriptors()

The Object.getOwnPropertyDescriptors() method returns all own property descriptors of a given object.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const descriptors = Object.getOwnPropertyDescriptors(person);

console.log(descriptors);
// {
//   firstName: { value: "John", writable: true, enumerable: true, configurable: true },
//   lastName: { value: "Doe", writable: true, enumerable: true, configurable: true },
//   age: { value: 30, writable: true, enumerable: true, configurable: true }
// }

14. Object.getPrototypeOf()

The Object.getPrototypeOf() method returns the prototype of the specified object.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const proto = Object.getPrototypeOf(person);

console.log(proto); // Object.prototype

15. Object.setPrototypeOf()

The Object.setPrototypeOf() method sets the prototype of a specified object to another object or null.
const person = { firstName: "John", lastName: "Doe", age: 30 };
const newProto = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

Object.setPrototypeOf(person, newProto);
console.log(person.fullName()); // "John Doe"

16. Object.seal()

The Object.seal() method prevents new properties from being added to an object and marks all existing properties as non-configurable. Property values can still be changed if they are writable.
const person = { firstName: "John", lastName: "Doe", age: 30 };
Object.seal(person);

person.age = 31; // This will work
console.log(person.age); // 31

delete person.lastName; // This will have no effect
console.log(person.lastName); // "Doe"

17. Object.isSealed()

The Object.isSealed() method determines if an object is sealed.
const person = { firstName: "John", lastName: "Doe", age: 30 };
console.log(Object.isSealed(person)); // false

Object.seal(person);
console.log(Object.isSealed(person)); // true

18. Object.preventExtensions()

The Object.preventExtensions() method prevents any extensions of an object. It disallows adding new properties to an object while still allowing modification and deletion of existing properties.
const person = { firstName: "John", lastName: "Doe", age: 30 };
Object.preventExtensions(person);

person.age = 31; // This will work
console.log(person.age); // 31

person.nationality = "American"; // This will have no effect
console.log(person.nationality); // undefined

Event Emitter

EventEmitter is a core module in Node.js that allows you to create and manage custom events in a simple and efficient way. It is particularly useful in building event-driven applications or when you need to decouple components in a system. Although it's not part of standard JavaScript, you can use it in Node.js applications or in the browser with some libraries that implement the EventEmitter pattern.
Here's a basic overview of how EventEmitter works in Node.js:
  1. Import the 'events' module: To use EventEmitter, you need to import the 'events' module provided by Node.js.
javascriptCopy code
const EventEmitter = require('events');

  1. Create a custom event emitter: Create a new instance of the EventEmitter class to create your custom event emitter.
javascriptCopy code
const myEventEmitter = new EventEmitter();

  1. Register event listeners: Use the 'on' method to register event listeners. An event listener is a function that gets executed when the specified event is emitted. You can register multiple listeners for the same event.
javascriptCopy code
myEventEmitter.on('eventName', (data) => {
  console.log('Event triggered:', data);
});

  1. Emit events: Use the 'emit' method to emit events. You can also pass data to the event listeners when emitting an event.
javascriptCopy code
myEventEmitter.emit('eventName', 'Some data');

  1. Remove event listeners: Use the 'removeListener' or 'removeAllListeners' methods to unregister event listeners.
javascriptCopy code
myEventEmitter.removeListener('eventName', listenerFunction);
// or
myEventEmitter.removeAllListeners('eventName');

Here's a complete example:
javascriptCopy code
const EventEmitter = require('events');
const myEventEmitter = new EventEmitter();

function handleEvent(data) {
  console.log('Event triggered:', data);
}

myEventEmitter.on('myEvent', handleEvent);

myEventEmitter.emit('myEvent', 'Hello, EventEmitter!');

myEventEmitter.removeListener('myEvent', handleEvent);

In this example, we create a custom event emitter, register a listener for the 'myEvent' event, emit the event with some data, and then remove the listener.
📒 Table of Contents
Var DeclarationDifferences between var and letAdvance OperatorsTernary OperatorTypeof OperatorNullish Coalescing OperatorOptional Chaining OperatorInstanceof OperatorIn OperatorSpread OperatorCopying ArraysConcatenating ArraysPassing Array Elements as Function ArgumentsRest OperatorThe Global Object in JavaScriptKey FeaturesEnvironmentsBest PracticesExampleAdditional ConsiderationsAvoid Polluting the Global NamespaceUse Global Object as a FallbackBe Aware of Platform DifferencesUse Strict ModeJavaScript HoistingHow Hoisting WorksFunction HoistingHoisting with let and constHow to Avoid Hoisting IssuesObjects: In-Depth ExplanationCreating Objects1. Object Literal2. Object Constructor3. Constructor Function4. Object.create()Accessing Object Information1. Dot Notation2. Bracket NotationEnumerating Object Properties1. For...in Loop2. Object.keys() and Object.values()3. Object.entries()Modifying ObjectsObject MethodsObject Prototypes and InheritanceStatic and Instance MethodsArrays: In-Depth ExplanationCreating ArraysAccessing Array ElementsManipulating ArraysMultidimensional ArraysArray IterationArray DestructuringObject Methods1. Object.assign()2. Object.create()3. Object.entries()4. Object.freeze()5. Object.isFrozen()6. Object.keys()7. Object.values()8. Object.hasOwnProperty()9. Object.is()10. Object.defineProperty()11. Object.defineProperties()12. Object.getOwnPropertyDescriptor()13. Object.getOwnPropertyDescriptors()14. Object.getPrototypeOf()15. Object.setPrototypeOf()16. Object.seal()17. Object.isSealed()18. Object.preventExtensions()Event Emitter
Suggestions
ES6