Coder Social home page Coder Social logo

javascript-programming-language's Introduction

javascript-programming-language

Javascript programming language guidelines/notebook

Javascript core basic

Working of JS Engine

Advanced Topics

Coding Concepts

Javascript coding practices/Challenges

Javascript Real-time code snippets

Javascript Core Basics

Variables and Data Types

Variables:

Containers for storing data values.

var name = 'Alice';    // ES5
let age = 25;          // ES6
const isStudent = true; // ES6

Refer to Hoisting to know more about variable differences. Hoisting

Data Types:

JavaScript supports various data types, including:

  • Primitive: string, number, boolean, null, undefined, symbol (ES6), bigint (ES11).
  • Non-primitive: object, array, function.

You can refer to call by value and call by difference to know the difference between data types.Call by value and call by reference

Operators

  • Arithmetic Operators: +, -, *, /, %, ++, --.
  • Comparison Operators: ==, ===, !=, !==, >, <, >=, <=.
  • Logical Operators: &&, ||, !.

Control Structures

Conditionals:

if, else if, else, switch.

if (age > 18) {
  console.log('Adult');
} else {
  console.log('Minor');
}

Loops:

for, while, do...while, for...of (ES6), for...in.

for ...in loop

Purpose: The for...in loop is used to iterate over the enumerable properties of an object (including inherited properties).

Usage: It is commonly used to loop through the properties of an object.

Iterates Over: Keys (property names) of the object.

Example

const person = { name: 'Alice', age: 25, city: 'New York' };

for (let key in person) {
  console.log(`${key}: ${person[key]}`);
}

for ...of loop

Purpose: The for...of loop is used to iterate over iterable objects (like arrays, strings, maps, sets, etc.).

Usage: It is commonly used to loop through the values of an iterable object.

Iterates Over: Values of the iterable.

const numbers = [1, 2, 3, 4, 5];

for (let number of numbers) {
  console.log(number);
}

Remember strings are also iterable objects so you can use for ...of to iterate string Refer - String

Use for...of when you need to access the values of an iterable object like an array, string, Map, Set, etc.

Inherited Properties:

for...in will iterate over all enumerable properties, including those inherited through the prototype chain.

for...of only iterates over the values in the iterable object and does not consider inherited properties.

Example with prototype:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.city = 'New York';

const person = new Person('Alice', 25);

for (let key in person) {
  console.log(`${key}: ${person[key]}`);
}

forEach loop

Loop-breaking statements

loop breaking statements are used to alter the flow of loop iterations.

Two primary statements are used for this purpose: break and continue.

break statement

The break statement is used to terminate the loop immediately, regardless of the iteration count or condition.

When a break statement is encountered inside a loop, the loop stops executing, and the control is transferred to the statement immediately following the loop.

for (let i = 0; i < 10; i++) {
  if (i === 5) {
    break; // Exit the loop when i equals 5
  }
  console.log(i);
}

continue Statement

The continue statement is used to skip the current iteration of the loop and move to the next iteration.

When a continue statement is encountered, the loop continues with the next iteration, bypassing the remaining code inside the loop for the current iteration.

for (let i = 0; i < 10; i++) {
  if (i % 2 === 0) {
    continue; // Skip even numbers
  }
  console.log(i);
}

Nested loops

In nested loops, break and continue only affect the loop they are in. To break out of an outer loop, you can use labeled statements.

Labeled Statements: Allow breaking out of or continuing specific loops in nested loop structures

outerLoop: for (let i = 0; i < 3; i++) { // labeled statement
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outerLoop; // Exit both loops
    }
    console.log(`i = ${i}, j = ${j}`);
  }
}

Functions

Function Declaration

A function declaration defines a named function using the function keyword.

Function declarations are hoisted, which means they can be called before they are defined in the code.

Hoisting: Function declarations are hoisted to the top of their scope, so they can be called before they are defined. Refer - Hoisting Named Functions: Function declarations must have a name. Can be Recursive: Named functions can call themselves recursively.

function greet(name) {
  return 'Hello, ' + name;
}

Function Expression

A function expression defines a function inside an expression.

Function expressions can be named or anonymous.

They are not hoisted, so they cannot be called before they are defined.

Not Hoisted: Function expressions are not hoisted, so they must be defined before they are called. Anonymous or Named: Function expressions can be anonymous or have a name. Useful for Closures: Function expressions are commonly used in closures and IIFEs (Immediately Invoked Function Expressions). Refer to Closures and IIFEs

const greet = function(name) {
  return 'Hello, ' + name;
};

Arrow functions(ES6)

Arrow functions, introduced in ES6, provide a shorter syntax for writing function expressions. They have a different behavior regarding the this keyword compared to regular functions.

Shorter Syntax: Arrow functions have a concise syntax. Implicit Return: If the function body consists of a single expression, it can return the value implicitly without the return keyword.

const greet = name => `Hello, ${name}!`;

No this Binding: Arrow functions do not have their own this context; they inherit this from the enclosing lexical scope.

const greet = (name) => 'Hello, ' + name;

Cannot be Used as Constructors: Arrow functions cannot be used with the new keyword to create instances.

const Person = (name) => {
  this.name = name;
};

const alice = new Person('Alice'); // TypeError: Person is not a constructor

No arguments Object: Arrow functions do not have their own arguments object. They inherit arguments from the parent scope.

function regularFunction() {
  console.log(arguments);
}

const arrowFunction = () => {
  console.log(arguments);
};

regularFunction(1, 2, 3); // [1, 2, 3]
arrowFunction(1, 2, 3);   // ReferenceError: arguments is not defined

image

Objects and arrays

  • Objects: Collections of key-value pairs.
`const user = {
  name: 'Alice',
  age: 25,
  greet: function() {
    return 'Hello, ' + this.name;
  }
};
console.log(user.greet()); // Hello, Alice

Ways to Loop through objects. Loop through objects

  • Arrays: Ordered collections of values.
`const numbers = [1, 2, 3, 4, 5];
console.log(numbers[0]); // 1

Asynchronous JavaScript

Callbacks:

Functions passed as arguments to other functions.

function fetchData(callback) {
  setTimeout(() => {
    callback('Data loaded');
  }, 1000);
}
fetchData(data => console.log(data));

Promises:

Represent future values or errors.

`const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Data loaded'), 1000);
});
promise.then(data => console.log(data));

Async/Await (ES8):

Syntactic sugar for working with Promises.

async function fetchData() {
  const data = await promise;
  console.log(data);
}
fetchData();

Refer Callbacks-promises-asyncs-await

Strings

A string is a sequence of characters used to represent text. Strings are one of the fundamental data types in JavaScript

Declaration: Strings can be declared using single quotes (') or double quotes (").

String Methods: JavaScript provides a variety of built-in methods to manipulate strings, such as toUpperCase(), toLowerCase(), charAt(), substring(), slice(), indexOf(), replace(), trim(), split(), etc.

Image representations

string methods js

Template Literals: Introduced in ES6, template literals allow for more flexible string formatting and interpolation(process of inserting something into something else) using backticks () and ${} placeholders.

Refer - string related tasks

Dates Handling

Date functions are methods that allow you to work with dates and times. These functions are part of the built-in Date object in JavaScript.
Refer - Date related tasks

Creating a Date Object:

new Date(): Creates a new Date object representing the current date and time.

new Date(milliseconds): Creates a new Date object from the number of milliseconds since January 1, 1970, 00:00:00 UTC (the Unix Epoch).

new Date(dateString): Creates a new Date object from a date string.

new Date(year, month, day, hours, minutes, seconds, milliseconds): Creates a new Date object with the specified date and time components.

Getting Date Components:

getDate(), getMonth(), getFullYear(): Get the day of the month, month (0-11), and year, respectively.

getDay(): Get the day of the week (0-6, where 0 is Sunday).

getHours(), getMinutes(), getSeconds(), getMilliseconds(): Get the hours, minutes, seconds, and milliseconds, respectively.

Setting Date Components:

setDate(day), setMonth(month), setFullYear(year): Set the day of the month, month (0-11), and year, respectively.

setHours(hours), setMinutes(minutes), setSeconds(seconds), setMilliseconds(milliseconds): Set the hours, minutes, seconds, and milliseconds, respectively.

Formatting Dates:

toDateString(), toISOString(), toLocaleDateString(), toLocaleString(), toLocaleTimeString(), toString(), toTimeString(), toUTCString(): These methods convert a Date object to various string representations according to different formatting rules and locales.

Map and Set

Map and Set are two built-in objects introduced in ECMAScript 2015 (ES6) that provide additional ways to manage collections of data.

They offer more sophisticated capabilities compared to plain objects and arrays, particularly in terms of key-value pair handling and ensuring uniqueness.

WeakMap and WeakSet are special types of collections in JavaScript introduced in ECMAScript 2015 (ES6).

They are similar to Map and Set but have some key differences, particularly in terms of how they handle references to objects and their garbage collection behavior.

Map

A Map is an ordered collection of key-value pairs, where keys can be of any type (including objects, functions, and primitive types).

It maintains the insertion order of its elements, meaning that when you iterate over the keys, they appear in the order they were inserted.

Basic Operations: Creating a Map, Adding Elements, Retrieving Elements, Checking for Existence, Removing Elements, Getting the Size, and Clearing All Elements.

const myMap = new Map();

myMap.set('a', 1);
myMap.set('b', 2);

console.log(myMap.get('a')); // 1

console.log(myMap.has('b')); // true

myMap.delete('a');

console.log(myMap.size); // 1

myMap.clear();

Iteration You can iterate over the keys, values, or entries of a Map.

const myMap = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

// Iterating over keys
for (const key of myMap.keys()) {
  console.log(key); // 'a', 'b', 'c'
}

// Iterating over values
for (const value of myMap.values()) {
  console.log(value); // 1, 2, 3
}

// Iterating over entries
for (const [key, value] of myMap.entries()) {
  console.log(key, value); // ['a', 1], ['b', 2], ['c', 3]
}

WeekMap

A WeakMap is a collection of key-value pairs where the keys are objects and the values can be arbitrary values.

The key feature of a WeakMap is that it holds "weak" references to the keys, which means that if there are no other references to the key object, it can be garbage collected.

Characteristics

Keys must be objects: Primitive data types (e.g., strings, numbers) cannot be used as keys.

No size property: WeakMap does not have a size property or any method to get the number of entries.

No iterations: You cannot iterate over the entries of a WeakMap (no keys(), values(), entries(), or forEach() methods).

Garbage collection: If the key object is no longer reachable from the program, it can be garbage collected, and its entry in the WeakMap will be removed.

Methods

set(key, value): Adds or updates an entry with the specified key and value.

get(key): Returns the value associated with the specified key, or undefined if the key is not in the WeakMap.

has(key): Returns true if an entry with the specified key exists, otherwise false.

delete(key): Removes the entry with the specified key.

Example:

let wm = new WeakMap();
let obj = {};

// Adding an entry
wm.set(obj, 'some value');
console.log(wm.get(obj)); // 'some value'

// Checking for an entry
console.log(wm.has(obj)); // true

// Removing an entry
wm.delete(obj);
console.log(wm.has(obj)); // false

Set

A Set is a collection of unique values. It can contain any type of value (primitives or object references) and automatically removes duplicate values.

Basic Operations: Creating a Set, Adding Elements, Checking for Existence, Removing Elements, Getting the size, and Clearing all elements

const mySet = new Set();

mySet.add(1);
mySet.add(2);
mySet.add(2); // Duplicate values are ignored

console.log(mySet.has(1)); // true

mySet.delete(2);

console.log(mySet.size); // 1

mySet.clear();

Iteration: You can iterate over the values of a Set.

const mySet = new Set([1, 2, 3]);

// Iterating over values
for (const value of mySet) {
  console.log(value); // 1, 2, 3
}

WeekSet

A WeakSet is a collection of objects, where each object can occur only once in the set.

Like WeakMap, a WeakSet holds weak references to its objects, allowing for garbage collection when there are no other references to an object.

Characteristics

Only objects as values: Primitive data types cannot be added to a WeakSet.

No size property: WeakSet does not have a size property or any method to get the number of elements.

No iterations: You cannot iterate over the elements of a WeakSet (no keys(), values(), entries(), or forEach() methods).

Garbage collection: If an object is no longer reachable from the program, it can be garbage collected, and its entry in the WeakSet will be removed.

Methods

add(value): Adds a new object to the WeakSet.

has(value): Returns true if the object is in the WeakSet, otherwise false.

delete(value): Removes the specified object from the WeakSet.

Example

let ws = new WeakSet();
let obj = {};

// Adding an object
ws.add(obj);
console.log(ws.has(obj)); // true

// Removing an object
ws.delete(obj);
console.log(ws.has(obj)); // false

Difference between Map and Set

Map Set
Data Structure Stores key-value pairs Stores unique values
Key Types Keys can be any type (objects, functions, primitive types) Only stores values, which must be unique.
Order of Elements Maintains the insertion order of key-value pairs Maintains the insertion order of values
Methods set, get, has, delete, clear, keys, values, entries add, has, delete, clear, keys, values, entries

Dom Manuipulation APIs

DOM (Document Object Model) manipulation APIs in JavaScript allow developers to interact with and modify the structure, style, and content of web documents. These APIs provide methods to create, remove, change, and traverse elements and their attributes in the DOM.

1. Selecting Elements

getElementById Select an element by its ID.

const element = document.getElementById('myId');

getElementsByClassName Selects elements by their class name. Returns a live HTMLCollection.

const elements = document.getElementsByClassName('myClass');

getElementsByTagName Selects elements by their tag name. Returns a live HTMLCollection.

const elements = document.getElementsByTagName('div');

querySelector Selects the first element that matches a CSS selector.

const element = document.querySelector('.myClass');

querySelectorAll Selects all elements that match a CSS selector. Returns a static NodeList.

const elements = document.querySelectorAll('.myClass');

2. Modifying Element Content

innerHTML Gets or sets the HTML content inside an element.

element.innerHTML = '<p>New content</p>';

textContent Gets or sets the text content inside an element.

`element.textContent = 'New text content';

3. Modifying Element Attributes

getAttribute Gets the value of an attribute on the specified element.

const value = element.getAttribute('src');

setAttribute Sets the value of an attribute on the specified element.

element.setAttribute('src', 'newImage.jpg');

removeAttribute Removes an attribute from the specified element.

element.removeAttribute('src');

4. Modifying Element Styles

style Directly modifies the CSS styles of an element.

element.style.color = 'blue';
element.style.fontSize = '20px';

Adding and Removing Classes

`classList Provides methods to add, remove, and toggle classes.

element.classList.add('newClass');
element.classList.remove('oldClass');
element.classList.toggle('active');

6. Creating and Inserting Elements

createElement Creates a new element.

const newElement = document.createElement('div');

appendChild Adds a new child node to the end of a specified parent node.

parentElement.appendChild(newElement);

insertBefore Inserts a new node before a specified existing node.

parentElement.insertBefore(newElement, referenceElement);

removeChild Removes a child node from the DOM.

parentElement.removeChild(childElement);

replaceChild Replaces a child node with a new node.

parentElement.replaceChild(newElement, oldElement);

7. Event Handling

addEventListener Attaches an event handler to an element.

element.addEventListener('click', function() {
  console.log('Element clicked!');
});

removeEventListener Removes an event handler from an element.

element.removeEventListener('click', eventHandlerFunction);

8. Traversing the DOM

parentNode Gets the parent node of an element.

const parent = element.parentNode;

**childNodes** Gets a collection of a node's child nodes, including text nodes and comments.

const children = element.childNodes;

firstChild and `lastChild Gets the first and last child nodes of an element.

const first = element.firstChild;
const last = element.lastChild;

nextSibling and previousSibling Gets the next and previous sibling nodes of an element.

const next = element.nextSibling;
const previous = element.previousSibling;

children Gets a live HTMLCollection of the element's child elements (excluding text and comment nodes).

const children = element.children;

firstElementChild and lastElementChild Gets the first and last child elements.

const first = element.firstElementChild;
const last = element.lastElementChild;

nextElementSibling and previousElementSibling Gets the next and previous sibling elements.

const next = element.nextElementSibling;
const previous = element.previousElementSibling;

Web storage in JS

Since HTML5 was introduced, we have had a variety of methods for caching or storing data on the client browser.

Web storage - Although it operates on similar concepts to server-side storage, browser storage or client-side storage has various use cases. It is made up of JavaScript APIs that enable us to store data on the client (i.e., on the user's computer), where it may later be retrieved as needed.

Cookies, local storage, and session storage are the three methods most frequently used to save data locally on browsers.

Javascript could access web storage, and the server couldn't read any of this information unless it was manually included in the request.

HTML web storage offers two objects for data storage on the client:

  • Data is stored in local storage objects that have no expiration dates.
  • Data is stored for one session in a session storage object (data is lost when the browser tab is closed).

Local Storage

We can store data as key/value pairs in a web browser using the local storage web storage technique on the client's computer.

Unless the user explicitly deletes it from the browser, the data is kept in local storage forever

There are four ways to set, retrieve, remove, and clear data from local storage:

  • To set the data in local storage, use the setItem() method. The parameters for this method are key and value. With this method, we can store value with a key. localStorage.setItem(key, value);
  • We can use the getItem() method to access the data kept in local storage. The key whose value we need to retrieve is the sole parameter for this procedure. localStorage.getItem(key);
  • The removeItem() method, which is kept in memory with the key, allows us to delete the data. localStorage.removeItem(key);
  • All of the data kept in the local storage can be cleared using the clear() method.

Depending on our use case, there are advantages and disadvantages to using local storage

Advantages

  • There is no expiration date for the data kept in local storage.
  • The storage limitation is approximately 10MB.
  • Data from local storage is never sent to the server.

Disadvantages

  • Since local storage data is in plain text, it is not designed to be secure.
  • Since the data type is restricted to strings, serialization is required.
  • Only the client side, not the server side, is capable of reading data.

Session Storage

The localStorage and the sessionStorage are extremely similar. However, the primary distinction is in how long information stays in the browserโ€”until the current tab or session is active.

The data stored in session storage is also deleted when you close the tab or end the session.

Using the setItem() and getItem() methods, we can also set and get session data, just like we do with local storage.

Cookie

Cookies assist us in storing client-side data to provide website visitors with a customized experience. Cookies are transmitted to the server along with requests and returned to the client in response; as a result, the server and client exchange cookie data with each request.

The servers could deliver user-tailored content using the cookie data.

To mitigate a few security vulnerabilities like cross-site scripting, we have an HTTPOnly cookie flag that may be used to limit cookie access in JavaScript (the cookies are only available for servers to access).

Session Cookies - Session cookies are deleted when the browser is closed because they do not contain attributes such as Expires or Max-Age.

Session Cookies - Session cookies are deleted when the browser is closed because they do not contain attributes such as Expires or Max-Age.

image

Advanced Topics

Event Loop, Microtasks, Macrotasks

In Node.js, understanding the event loop, microtasks, and macro tasks (also known as task queues) is crucial for comprehending how asynchronous operations are handled.

Event Loop

The event loop is the mechanism that handles asynchronous operations in Node.js. It continuously checks the call stack and the callback queue, executing tasks from non-blocking queues. The event loop has several phases, each handling different types of callbacks. These phases include:

  • Timers: Executes callbacks for setTimeout and setInterval.
  • I/O Callbacks: Executes callbacks for I/O operations, such as reading from a file.
  • Idle, Prepare: Internal use only.
  • Poll: Retrieves new I/O events; executes I/O-related callbacks (excluding timers, close callbacks, and setImmediate).
  • Check: Executes callbacks for setImmediate.
  • Close Callbacks: Executes callbacks for closed events, like when a socket closes.

Macrotasks (Task Queue)

Macrotasks, or simply tasks, are scheduled in the task queue. This includes:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O tasks
  • UI rendering (in browsers)

Microtasks (Microtask Queue)

Microtasks, also known as the microtask queue, include:

  • process.nextTick
  • Promises (and other mechanisms using queueMicrotask) Microtasks are processed after the currently executing script and before the event loop continues to the next phase. This ensures that microtasks are handled immediately after the current operation is completed, giving them higher priority over macro tasks.

Execution order

  • Start the event loop.
  • Execute all tasks in the call stack.
  • Process the microtask queue:
  • Run all microtasks (e.g., resolved promises, process.nextTick).
  • Move to the next phase of the event loop.
  • Execute all callbacks in the appropriate phase (timers, I/O, etc.).
  • Repeat steps 2-5.

Events: DOMContentLoaded, load, beforeunload, unload

In JavaScript, particularly in web development, the DOMContentLoaded, load, beforeunload, and unload events are crucial for managing the lifecycle of a web page.

DOMContentLoaded
The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. This event is useful if you want to run some JavaScript code as soon as the DOM is ready.

load
The load event fires when the whole page has loaded, including all dependent resources such as stylesheets, images, and iframes. This event is useful when you need to ensure that everything on the page is fully loaded before executing your code.

beforeunload
The beforeunload event is fired when the window, the document, and its resources are about to be unloaded. This event is typically used to show a confirmation dialog, asking the user if they are sure they want to leave the page. However, the exact implementation and appearance of this dialog are controlled by the browser, and not all browsers may honor the custom message.

unload
The unload event is fired when the document or a child resource is being unloaded. This can happen, for example, when the user navigates away from the page. The unload event is not commonly used because it cannot reliably prevent the page from being unloaded.

document.addEventListener('DOMContentLoaded', function() {
  console.log('DOM fully loaded and parsed');
});

window.addEventListener('load', function() {
  console.log('Page fully loaded');
});

window.addEventListener('beforeunload', function(event) {
  event.preventDefault(); // Standard way to prevent default behavior
  event.returnValue = ''; // Some browsers require this property to be set
});

window.addEventListener('unload', function() {
  console.log('Page is unloading');
});

Polyfills

(Mostly asked polyfills: Promise, Promise.all, Promise.any, Promise.race, Promise.allSettled, call, apply, bind, map, reduce, filter, forEach, flat, fetch API)

A polyfill is a piece of code (usually JavaScript) used to provide modern functionality on older browsers that do not natively support it.

Polyfills emulate newer web standards in older environments, allowing developers to use modern features without worrying about compatibility issues across different browsers and versions.

How Polyfills Work
Polyfills typically check if a feature is already available in the browser. If it is not, they define the functionality themselves. This conditional definition ensures that the polyfill does not overwrite native implementations if they exist.

Array.prototype.includes Polyfill

The Array.prototype.includes method was introduced in ECMAScript 2016 (ES7). Older browsers like Internet Explorer do not support it. Here's a polyfill for this method:

if (!Array.prototype.includes) {
  Array.prototype.includes = function(element) {
    return this.indexOf(element) !== -1;
  };
}

promise Polyfill

Promises are part of ECMAScript 2015 (ES6). A polyfill can implement this feature in environments where it is not available:

if (typeof Promise === 'undefined') {
  // Include a Promise polyfill library like `es6-promise`
  (function() {
    // Polyfill code here
  })();
}

Using Polyfill Libraries

There are many libraries available that provide polyfills for a wide range of features. Some popular ones include:

  • core-js: Provides polyfills for ECMAScript features.
  • babel-polyfill: Includes core-js and regenerator-runtime to emulate a full ES2015+ environment.
  • html5shiv: Adds HTML5 element support to older versions of Internet Explorer.
  • polyfill.io: A service that provides polyfills based on the user's browser.

To use a polyfill, you can include the polyfill script in your HTML file before your main JavaScript code.

Alternatively, you can install polyfills using npm and include them in your build process if you are using a module bundler like Webpack

Memoization

Memoization is a programming technique used to optimize the performance of functions by caching the results of expensive function calls and returning the cached result when the same inputs occur again. This can significantly improve the efficiency of functions that are called repeatedly with the same arguments.

How Memoization Works

  • Cache Initialization: Create a cache (usually an object or a map) to store the results of function calls.
  • Cache Check: When the memoized function is called, first check if the result for the given inputs is already in the cache.
  • Return Cached Result: If the result is in the cache, return it immediately.
  • Compute and Cache Result: If the result is not in the cache, compute it, store it in the cache, and then return it.

Example: Fibonacci Sequence

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

function fib(n) {
  if (n <= 1) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}

const memoizedFib = memoize(fib);

console.log(memoizedFib(40)); // Computed and cached
console.log(memoizedFib(40)); // Retrieved from cache

Generator Functions

Generator functions in JavaScript are a special class of functions that can be paused and resumed, allowing for the generation of a sequence of values over time, instead of computing all values at once and sending them back.

A generator function is defined using the function* syntax, and it uses the yield keyword to pause execution and produce a value.

function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}
  • Function Declaration: The function* syntax is used to declare a generator function.
  • Yield: The yield keyword is used to pause the function and return a value.
  • Iterator: Calling a generator function returns an iterator object.

next() Method The iterator object returned by a generator function has a next() method. This method resumes the execution of the generator function and returns an object with two properties:

  • value: The value yielded by the yield expression.
  • done: A boolean indicating whether the generator has been completed.

Example with a Loop

function* idMaker() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const gen = idMaker();

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Asynchronous Generators With the introduction of ES2018, JavaScript supports asynchronous generators, which can yield promises and work with the for await...of loop:

async function* asyncGenerator() {
  let i = 0;
  while (i < 3) {
    yield new Promise(resolve => setTimeout(() => resolve(i++), 1000));
  }
}

(async () => {
  for await (let num of asyncGenerator()) {
    console.log(num); // 0, 1, 2 (with a 1-second delay between each)
  }
})();

Use Cases

  1. Iterators and Iteration Protocols

Generators provide an easy way to create custom iterators. This is useful when you need to iterate over complex data structures or generate values on the fly.

function* customIterator(arr) {
  for (let i = 0; i < arr.length; i++) {
    yield arr[i];
  }
}

const iterator = customIterator([1, 2, 3, 4, 5]);
for (const value of iterator) {
  console.log(value); // 1, 2, 3, 4, 5
}
  1. Asynchronous Programming
  2. Lazy Evaluation: Generators can be used to implement lazy evaluation, where values are computed only when needed.
  3. State Machines: Generators can be used to implement state machines, where each state can yield to the next state. This can make the code for state transitions more readable and maintainable.
  4. Infinite Scrolling or Pagination Generators can handle data fetching in chunks, which is useful for implementing features like infinite scrolling or pagination in web applications.
function* fetchPages(url) {
  let page = 1;
  while (true) {
    const data = yield fetch(`${url}?page=${page}`).then(res => res.json());
    page++;
    if (!data || data.length === 0) break;
  }
}

const pageFetcher = fetchPages('https://api.example.com/data');

async function loadMore() {
  const { value, done } = await pageFetcher.next();
  if (!done) {
    console.log(value);
    // Render data and load more if necessary
  }
}

loadMore();
  1. Pausing and Resuming Execution
  2. Combinatorial Generation

Iterators

In JavaScript, an iterator is an object that allows you to traverse through a collection of items, one at a time.

The iterator protocol defines a standard way for objects to produce a sequence of values.

An object becomes an iterator by implementing a next method that returns an object with two properties: value and done.

Iterator Protocol

To be an iterator, an object must implement the following method:

next(): A method that returns an object with two properties:

  • value: The next value in the iteration sequence.
  • done: A boolean indicating whether the sequence is complete.
function createIterator(arr) {
  let index = 0;
  return {
    next: function() {
      if (index < arr.length) {
        return { value: arr[index++], done: false };
      } else {
        return { value: undefined, done: true };
      }
    }
  };
}

const iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Iterable Protocol
An object is considered iterable if it implements the @@iterator method, which is available via the [Symbol.iterator] key. This method should return an iterator.

Built-in Iterables
Several built-in objects in JavaScript are iterable by default, including:

  • Arrays
  • Strings
  • Maps
  • Sets
  • Typed arrays
  • The arguments object

Custom Iterators with Generators
Generators provide a simpler way to create custom iterators. The function* syntax is used to define a generator function, which automatically implements the iterable protocol.

Web APIs

JavaScript has access to a wide range of Web APIs that allow it to interact with the browser and other services.

These APIs enable functionalities such as manipulating the DOM, making network requests, storing data locally, accessing device hardware, and much more.

  • DOM (Document Object Model) APIs: Allows access to the HTML document structure and elements, provides methods for manipulating HTML elements, and Manages events and event handling
  • Fetch API: Provides a modern way to make network requests.
  • Storage APIs: local storage, session storage and indexedDB.
  • Geolocation API: Retrieves the geographical location of the user's device.
  • Canvas API: Used for drawing graphics via JavaScript
  • Web Storage API: Local storage and session storage.
  • WebSockets API: Provides full-duplex communication channels over a single TCP connection.
  • Service Workers API: Allows background scripts to handle network requests, cache resources, and provide offline functionality.
  • Notifications API: Allows web pages to send notifications to the user's device.
  • Device APIs: Provides information about the battery status of the device. Provides information about the physical orientation of the device.
  • Media APIs: Provides access to media input devices like cameras and microphones.
  • Clipboard API: Provides a way to read from and write to the clipboard.
  • Payment Request API: Provides a way to handle payments in web applications.
  • Performance API: Provides methods to measure the performance of the web application.
  • Drag and Drop API: Provides the functionality to create draggable elements.
  • File API: Allows web applications to read the contents of files.
  • Fullscreen API: Allows an element to be displayed in fullscreen mode.
  • History API: Provides access to the browser's session history.
  • Speech API: Converts text to speech, Converts speech to text.
  • Web Animations API: Element.animate(): Provides a way to animate elements.

Design Patterns

Design patterns are reusable solutions to common problems in software design. They represent best practices that have evolved and are widely accepted by developers. design patterns can help create more maintainable, flexible, and scalable code.

Creational Patterns

Structural Patterns

Behavioral Patterns

  • Observer
  • Command
  • Strategy Pattern

Singleton Pattern

The Singleton pattern ensures a class has only one instance and provides a global point of access to it.

const Singleton = (function() {
  let instance;

  function createInstance() {
    return new Object("I am the instance");
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

Factory Pattern

The Factory pattern provides a way to create objects without specifying the exact class of object that will be created.

class Car {
  constructor() {
    this.type = "car";
  }
}

class Truck {
  constructor() {
    this.type = "truck";
  }
}

class VehicleFactory {
  createVehicle(vehicleType) {
    if (vehicleType === "car") {
      return new Car();
    } else if (vehicleType === "truck") {
      return new Truck();
    }
  }
}

const factory = new VehicleFactory();
const car = factory.createVehicle("car");
const truck = factory.createVehicle("truck");
console.log(car.type); // car
console.log(truck.type); // truck

Module Pattern

The Module pattern encapsulates related code into a single unit of code, which can be used to avoid polluting the global scope.

seems like uses the closure function

const Module = (function() {
  let privateVar = "I am private";

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function() {
      privateMethod();
    }
  };
})();

Module.publicMethod(); // I am private

Decorator Pattern

The Decorator pattern allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.

class Car {
  constructor() {
    this.cost = function() {
      return 20000;
    };
  }
}

function carWithSunroof(car) {
  car.hasSunroof = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 1500;
  };
}

function carWithLeatherSeats(car) {
  car.hasLeatherSeats = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 2000;
  };
}

const myCar = new Car();
carWithSunroof(myCar);
carWithLeatherSeats(myCar);

console.log(myCar.cost()); // 23500
console.log(myCar.hasSunroof); // true
console.log(myCar.hasLeatherSeats); // true

Observer Pattern

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notifyObservers(message) {
    this.observers.forEach(observer => observer.update(message));
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }

  update(message) {
    console.log(`${this.name} received message: ${message}`);
  }
}

const subject = new Subject();
const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notifyObservers("Hello Observers!"); // Observer 1 received message: Hello Observers! // Observer 2 received message: Hello Observers!

Working of JS Engine

JavaScript engines are responsible for interpreting and executing JavaScript code in web browsers, servers (Node.js), or other environments where JavaScript runs. Understanding how a JavaScript engine works involves grasping its key components and the process it follows to execute JavaScript code efficiently.

Key Components of a JavaScript Engine:

Parser: The parser reads the JavaScript code and converts it into an Abstract Syntax Tree (AST) representation. This tree structure represents the grammatical structure of the code.

Interpreter: The interpreter translates the AST into machine code or bytecode directly executable by the computer's processor. This step involves converting JavaScript instructions into low-level instructions that the computer can understand and execute.

Compiler: Some JavaScript engines use a compiler to optimize code performance. The compiler can perform various optimizations like inlining functions, reordering code, and eliminating unused code paths to improve execution speed.

Memory Heap: The memory heap is where objects (including function objects and closures) are allocated when memory is requested during code execution. Memory management is crucial to prevent memory leaks and optimize performance.

Call Stack: The call stack keeps track of the currently executing functions. When a function is called, it is pushed onto the stack. When the function completes, it is popped off the stack. This mechanism manages the execution context of JavaScript code, including function calls and returns.

Execution Process of JavaScript Code:

  • Parsing: The JavaScript engine starts by parsing the source code to create an AST.
  • Compilation: The AST is then compiled into bytecode or machine code. In modern engines, compilation may occur just-in-time (JIT), where code is optimized during runtime.
  • Execution: The compiled code is executed line by line. The call stack manages the order and context of function calls.
  • Memory Management: The engine manages memory allocation and deallocation to store variables, objects, and function references.

JavaScript Engine Examples

  • V8 (Chrome, Node.js): Developed by Google, V8 is a high-performance engine that compiles JavaScript directly into native machine code.
  • SpiderMonkey (Firefox): Mozilla's JavaScript engine that utilizes both interpreted and JIT compilation techniques.
  • JavaScriptCore (Safari): Apple's engine that includes the Nitro JIT compiler for fast execution.
  • Chakra (Microsoft Edge): Originally used in Edge, now replaced by the Chromium-based engine in newer versions.

Optimizations:

  • Just-in-Time (JIT) Compilation: Dynamically compiles frequently executed code into native machine code for faster execution.
  • Inline Caching: Optimizes property access by caching property lookups.
  • Garbage Collection: Manages memory automatically, reclaiming unused memory to prevent leaks.

Coding concepts

1. Currying

Definition:

Currying is a fundamental concept in JavaScript that involves transforming a function with multiple arguments into a series of functions, each taking a single argument. This makes the code more flexible and reusable.

Syntax:

  • It creates a chain of functions, each taking a single argument and returning a new function that waits for the next argument
  • This chaining continues until all the arguments are provided, and the final function produces the desired result.

Challenge 1: Currency converter

const convertCurrency = (conversionRate) => (fromCurrency) => (toCurrency) => (amount) => {
const convertedAmount = ( amount * conversionRate[fromCurrency][toCurrency]).toFixed(2);

return `${amount} ${fromCurrency} is equal to ${convertedAmount} ${toCurrency}`;
}

const usdToEur = convertCurrency({USD: { EUR: 0.85}});
console.log(usdToEur("USD")("EUR")(100));

Challenge 2: customer permissions

const checkPermission = permissions => resource => action => {
    if(permissions[resource] && permissions[resource].includes(action)){
        return `Permission granted: ${action} in ${resource}`;
    }
    
    return `Permission denied: ${action} in ${resource}`;
}

const userPermissions = checkPermission({files: ['read','write'], photos:['read']});

console.log(userPermissions("files")("read"));

Challenge 3: Functional composition

const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc),x);

const square = x => x * x;

const double = x => x * 2;

const addOne = x => x + 1;

const transform = pipe(square, double, addOne);

console.log(transform(3));
// result: 19 (square(3) => double(9) => addOne(18))

Currying vs Partial function

While both currying and partial functions involve breaking down functions, currying generates a chain of functions that each take one argument,
whereas partial functions fix some arguments and return a new function with fewer arguments. Here is a comparison: Currying

const multiply = a => b => a * b;

const double = multiply(2);
console.log(double(5));

Partial function This technique is often used for functional programming and can be implemented in various ways in JavaScript, such as using closures or bind() method.

const partialMultiply = (a, b) => a* b;
const double = x => partialMultiply(2,x);

console.log(double(5));

Factory functions are used to create and return instances of objects. Partial functions are functions created by fixing some arguments of another function, producing a new function with reduced arity (number of arguments).

2. Closures

Definition:

A closure is a function that is bundled together with its lexical environment.ie., It is a function that remembers the scope in which it was created, even if it is executed outside of the scope.

Syntax:

function outerClosureFunction (input) {
    let message ='count';
    const = count = 10;
    
    function inner() {
        return message + count;
    }
    
    return inner;
}

let closureObj = new outerClosureFunction(3);
console.log(closureObj());

Usecases

Data encapsulation

You can create private variables that can't be accessed from outside.

function Counter(){
let count=0;
return {
increment: function(){count++;},
getCount: function(){ return count;}
};
}

Factory functions:

A factory function is a function that returns an object. It is called a "factory" because it's designed to produce instances of objects, similar to how a factory produces goods. Sometimes factory functions return other functions, often with some arguments or internal variables set to specific values(closures). They are a way to generate functions dynamically but not all dynamic function generation is done through factory functions.

function multiplier(factor){
return x=> x*factor;
}

//example2
function createPerson(name, age) {
  return {
    name: name,
    age: age,
    greet: function() {
      console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
  };
}

let person1 = createPerson('Alice', 30);
let person2 = createPerson('Bob', 25);

Dynamic function generation

Dynamic function generation refers to the creation of functions at runtime based on certain conditions or parameters. This often involves closures because the dynamically generated functions might close over variables in their surrounding scope.

function curation:

Function curation is the process of taking a function with multiple arguments and producing from it a function that takes fewer arguments. The missing arguments are set to specific values. This is closely related to the concept of "partial application" in functional programming.

3. IIFE

An IIFE (Immediately Invoked Function Expression) is a JavaScript function that is executed right after it is defined.

It is used to create a local scope for variables and functions, preventing them from polluting the global namespace.

This is particularly useful for encapsulating code, creating private variables and methods, and implementing the module pattern. IIFEs can accept parameters, return values, and help in organizing and structuring code in a clean and modular way.

Example:

(function() {
  // code here
})();

Encapsulation: IIFEs help in creating a local scope for variables and functions, thus preventing them from polluting the global namespace.

Avoid Global Namespace Pollution: By using IIFEs, you can avoid defining too many global variables and functions.

Private Variables and Methods: IIFEs can be used to create private variables and methods.

var counter = (function() {
  var count = 0; // private variable

  return {
    increment: function() {
      count += 1;
      return count;
    },
    reset: function() {
      count = 0;
      return count;
    }
  };
})();

console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.reset()); // 0

Module Pattern: IIFEs are often used in the module pattern to create modules with private and public methods.

var myModule = (function() {
  var privateVar = "I am private";

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function() {
      privateMethod();
    }
  };
})();

myModule.publicMethod(); // I am private

4. Destructuring

Destructuring is a feature in JavaScript that allows you to extract values from arrays or properties from objects and assign them to variables in a more concise and readable way.

Destructuring Arrays

Array destructuring allows you to unpack values from arrays into distinct variables.

Basic Syntax

const [a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

Skipping Items

You can skip items in the array by leaving an empty space.

const [a, , b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 3

Default Values

You can provide default values in case the array does not have enough elements.

const [a, b = 5] = [1];
console.log(a); // 1
console.log(b); // 5

Using Rest Operator

You can use the rest operator (...) to collect the remaining elements into a new array.

const [a, ...rest] = [1, 2, 3, 4];
console.log(a); // 1
console.log(rest); // [2, 3, 4]

Destructuring Objects

Object destructuring allows you to unpack properties from objects into distinct variables.

Basic Syntax

const { name, age } = { name: 'John', age: 30 };
console.log(name); // John
console.log(age); // 30

Renaming Variables

You can rename variables while destructuring by using a colon (:).

const { name: fullName, age: years } = { name: 'John', age: 30 };
console.log(fullName); // John
console.log(years); // 30

Default Values

You can provide default values if the property does not exist in the object.

const { name, age = 25 } = { name: 'John' };
console.log(name); // John
console.log(age); // 25

Nested Destructuring

You can destructure nested objects.

const person = { name: 'John', address: { city: 'New York', zip: 10001 } };
const { name, address: { city, zip } } = person;
console.log(name); // John
console.log(city); // New York
console.log(zip); // 10001

Using Rest Operator

You can use the rest operator to collect the remaining properties into a new object.

const { name, ...rest } = { name: 'John', age: 30, city: 'New York' };
console.log(name); // John
console.log(rest); // { age: 30, city: 'New York' }

Destructuring Function Parameters

You can destructure arrays and objects directly in function parameters.

Array Destructuring in Function Parameters

function sum([a, b]) {
  return a + b;
}
console.log(sum([1, 2])); // 3

Object Destructuring in Function Parameters

function greet({ name, age }) {
  return `Hello, my name is ${name} and I am ${age} years old.`;
}
console.log(greet({ name: 'John', age: 30 })); // Hello, my name is John and I am 30 years old.

5. Inheritance

Protype inheritance

proto is a property of every variable that points to the parent object inherited from. In contrast, the prototype is a property of every constructor function that contains all stuff that its instance will inherit. So both are the same thing but act from different ends.

The difference between class inheritance and prototypal inheritance is that if any changes to class methods occur after creating some objects then those objects do not get the updated methods as their old/already created instances do not get updated because when we develop functions using this in the constructor are considered as properties, not as methods But, in prototypal inheritance, it will get updated to all the existing instances.

There are three ways to create prototype inheritance in js

Code sample - https://github.com/ColorCode/js-10-things/blob/main/1-inheritance/example.js

Class inheritance

Class inheritance in JavaScript is a mechanism that allows one class to inherit properties and methods from another class.

Inheritance enables the creation of new classes based on existing classes, where the new class (subclass or derived class) inherits the properties and methods of the existing class (superclass or base class).

JavaScript uses the extends keyword to establish an inheritance relationship between two classes, and the super keyword to call the constructor and methods of the superclass

// Superclass
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

// Subclass
class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Call the superclass constructor
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks.`);
  }

  getBreed() {
    console.log(`${this.name} is a ${this.breed}.`);
  }
}

const myDog = new Dog('Rex', 'German Shepherd');
myDog.speak();  // Rex barks.
myDog.getBreed(); // Rex is a German Shepherd.

Superclass

A superclass (or base class) is a class that is being inherited by another class. In the example above, Animal is the superclass. It contains a constructor to initialize the name property and a method speak().

Subclass

A subclass (or derived class) is a class that inherits from another class. In the example above, the Dog is the subclass inherited from the Animal. It uses the extends keyword to establish the inheritance.

The super Keyword

The super keyword is used within a subclass to call the constructor and methods of its superclass. This is necessary to initialize the inherited properties and allows the subclass to build upon the functionality of the superclass.

Overriding Methods

Subclasses can override methods from the superclass to provide specific implementations. In the example, the speak() method in Dog overrides the speak() method in Animal.

6. Spread and rest operator

The spread and rest operators in JavaScript are both represented by three consecutive dots (...).

Spread Operator

The spread operator is used to expand elements of an iterable (like an array or a string) into individual elements.

Use Cases

  1. Arrays: Copying Arrays, Merging Arrays, and adding elements to the array.
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
console.log(arr2); // [1, 2, 3]

const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

const newArr = [0, ...arr, 4];
console.log(newArr); // [0, 1, 2, 3, 4]
  1. Objects: Copying Objects, Merging Objects, Adding Properties to an Object
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 };
console.log(obj2); // { a: 1, b: 2 }

const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 2, c: 3, d: 4 }

const newObj = { ...obj, c: 3 };
console.log(newObj); // { a: 1, b: 2, c: 3 }
  1. Function Calls: The spread operator can be used to pass an array as arguments to a function.
function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

Rest Operator

The rest operator is used to gather a variable number of arguments into an array. It is primarily used in function parameter lists to handle an indefinite number of arguments.

Use cases

  1. Function Parameters: Gathering Remaining Arguments, Combining with Named Parameters.
function sum(...args) {
  return args.reduce((acc, val) => acc + val, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

function myFunction(a, b, ...rest) {
  console.log(a); // 1
  console.log(b); // 2
  console.log(rest); // [3, 4, 5]
}
myFunction(1, 2, 3, 4, 5);
  1. Array Destructuring: The rest operator can be used to collect the remaining elements of an array.
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
  1. Object Destructuring: The rest operator can also be used in object destructuring to collect remaining properties.
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 1
console.log(b); // 2
console.log(rest); // { c: 3, d: 4 }

7. Callback, Promises, and Async Await

Promises

syntax

let promise = new Promise(function(resolve, reject) {
  if(success)
      resolve("done!"), 1000);
      else reject("Not valid");
});

// resolve runs the first function in .then
promise.then(
  result => alert(result), // shows "done!" after 1 second
  error => alert(error) // doesn't run
);
// If weโ€™re interested only in errors, then we can use null as the first argument: .then(null, errorHandlingFunction). Or we can use .catch(errorHandlingFunction), which is exactly the same.
promise.catch(alert); // shows "Error: Whoops!" after 1 second

convert the above promise to Async and await

const returnSamplePromise = () => {
return promise;
}

async function convert() {
    let res = await returnSamplePromise();
}

convert();

8. Debouncing and Throttling

Debouncing

Debouncing ensures that a function is only called after a certain period has passed since it was last called.

If the event is triggered again within that period, the timer is reset.

Example use case: A search input field where the search function should only be called after the user has stopped typing for a specified period.

Throttling

Throttling ensures that a function is called at most once in a specified period.

Unlike debouncing, throttling ensures that the function is executed at regular intervals, regardless of how many times the event is triggered.

Example use case: Handling window resize events where the resize handler should only be called once every 100 milliseconds.

9. Event Propagation, Event Bubbling, Capturing, and Delegation

In JavaScript,
event propagation refers to the way events travel through the Document Object Model (DOM) hierarchy.
This process involves two main phases: capturing and bubbling.
Event delegation is a technique that leverages these phases to handle events more efficiently.

Event Propagation

Event propagation describes the order in which events are handled in the DOM.

  1. Capturing Phase (Capture Phase):

The event starts at the root of the DOM tree and travels down to the target element. During this phase, event listeners set for capturing can handle the event.

  1. Target Phase:

The event reaches the target element, where it originated. Event listeners on the target element can handle the event.

  1. Bubbling Phase:

After reaching the target, the event travels back up the DOM tree to the root. During this phase, event listeners set for bubbling can handle the event.

Event bubbling

Event bubbling refers to the process of the event traveling from the target element up through its ancestors. It allows higher-level elements to react to events fired on their descendants.

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

document.getElementById('parent').addEventListener('click', () => {
  console.log('Parent clicked');
});

Event Capturing

Event capturing is the opposite of event bubbling. The event travels from the root to the target element. To handle an event during the capturing phase, you need to set the useCapture parameter to true when adding the event listener.

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

document.getElementById('parent').addEventListener('click', () => {
  console.log('Parent clicked');
}, true);

// If you click on the child element, 'Parent clicked' will be logged before 'Child clicked', because the event is captured first by the parent.

Event Delegation

Event delegation is a technique that involves using a single event listener to manage all events of a particular type for multiple child elements.

This is particularly useful for managing events in a dynamic list of items, as it avoids the need to attach individual event listeners to each item.

By leveraging event bubbling, you can place a single event listener on a common ancestor and handle events for all its descendants.

document.getElementById('parent').addEventListener('click', (event) => {
  if (event.target && event.target.matches('li')) {
    console.log('List item clicked:', event.target);
  }
});

//Here, a single-click event listener is added to the parent element. When any li element inside the parent is clicked, the event bubbles up to the parent, where it is handled.

10. setTimeout, setInterval

setTimeout and setInterval are two essential functions in JavaScript for working with timers. They allow you to execute code after a delay (setTimeout) or repeatedly at specified intervals (setInterval).

setTimeout

The setTimeout function executes a specified function once after a given delay (in milliseconds).

let timeoutID = setTimeout(function, delay, arg1, arg2, ...);

//example
function sayHello() {
  console.log('Hello, World!');
}

// Execute sayHello after 2 seconds (2000 milliseconds)
let timeoutID = setTimeout(sayHello, 2000);

// If needed, clear the timeout before it executes
clearTimeout(timeoutID);

setInterval

The setInterval function repeatedly executes a specified function at fixed intervals (in milliseconds).

let intervalID = setInterval(function, delay, arg1, arg2, ...);

//example
function sayHello() {
  console.log('Hello, World!');
}

// Execute sayHello every 2 seconds (2000 milliseconds)
let intervalID = setInterval(sayHello, 2000);

// If needed, clear the interval to stop the repeated execution
clearInterval(intervalID);

setTimeout: Use clearTimeout(timeoutID) to prevent the function from being executed. setInterval: Use clearInterval(intervalID) to stop the repeated execution.

11. High Order Functions

Higher-order functions are functions that operate on other functions by taking them as arguments or returning them as results. In JavaScript, functions are treated as first-class citizens, which means they can be manipulated and passed around just like any other data type (e.g., strings, numbers).

  • Accepting Functions as Arguments: A higher-order function can take one or more functions as arguments.
  • Returning Functions: It can also return a function as its result.

map(), filter(), reduce(), forEach(): Array methods that accept callback functions.

setTimeout(), setInterval(): Functions that accept function callbacks to execute after a delay or at intervals.

Event handlers in DOM manipulation (addEventListener()).

First-order functions

First-order functions are functions that do not accept other functions as arguments or return functions as their output.

They are standard functions that operate solely with data and do not involve higher-order operations like passing functions around or returning functions.

  • Operate on Data
  • No functions are arguments
  • No functions are return values

Pure functions

A pure function in JavaScript is a function that, given the same input, will always return the same output and has no side effects. In other words, it adheres to two main principles:

  • Deterministic: The function always produces the same output for the same set of inputs. This ensures predictability and reliability in your code.

  • No Side Effects: The function does not modify variables outside of its scope (global variables) or perform any observable actions (such as modifying a mutable object passed as an argument) that could affect the calling scope or the program's state.

  • Input-Output Relationship: Pure functions rely only on their arguments (inputs) to produce a result (output). They do not depend on any external state that could change over time.

  • Referential Transparency: The concept that a function call can be replaced with its resulting value without changing the program's behavior. This property simplifies reasoning about code and enables optimizations.

// Pure function: It only depends on its arguments and returns a new value
function square(x) {
  return x * x;
}

console.log(square(3)); // Output: 9
console.log(square(3)); // Output: 9

12. Call, Apply, Bind

In JavaScript, call, apply, and bind are methods that allow you to manipulate how a function is called and how it executes. They provide ways to set the context (the value of this) and pass arguments to functions when invoking them.

1. call

The call method allows you to call a function with a given this value and arguments provided individually.

Syntax:

function.call(thisArg, arg1, arg2, ...);

thisArg: The value of this to be used when executing the function. arg1, arg2, ...: Optional arguments passed to the function.

const person = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

const person1 = {
  firstName: "John",
  lastName: "Doe"
};

const person2 = {
  firstName: "Jane",
  lastName: "Doe"
};

// Call fullName with person1 as context
console.log(person.fullName.call(person1)); // Output: "John Doe"

// Call fullName with person2 as context
console.log(person.fullName.call(person2)); // Output: "Jane Doe"

2. apply

The apply method is similar to call, but it accepts arguments as an array or an array-like object.

Syntax:

function.apply(thisArg, [argsArray]);

thisArg: The value of this to be used when executing the function. argsArray: An array or array-like object containing arguments to be passed to the function. Example:

const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);
console.log(max); // Output: 7

3. bind

The bind method creates a new function that, when called, has its this keyword set to a specified value, with a given sequence of arguments preceding any provided when the new function is called.

Syntax:

function.bind(thisArg[, arg1[, arg2[, ...]]]);

thisArg: The value of this to be used when executing the function. arg1, arg2, ...: Optional arguments that are prepended to arguments provided to the bound function when invoking it.

Example:

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // Output: undefined

const boundGetX = module.getX.bind(module);
console.log(boundGetX()); // Output: 42

KeyDifference

  1. Arguments Passing:

    • call and apply allow you to pass arguments individually or as an array/object, respectively.
    • bind creates a new function with pre-specified arguments that can be appended later.
  2. Immediate vs Delayed Execution:

    • call and apply immediately invoke the function with the provided context (this value).
    • bind returns a new function with the bound context (this value), which can be invoked later.
  3. Original Function vs New Function:

    • call and apply invoke the original function with the specified context and arguments.
    • bind creates a new function that, when invoked, uses the specified context.

13. Hoisting and Temporal dead zone

Hiosting

Hoisting is a JavaScript mechanism where variables, function declarations, and classes are moved to the top of their containing scope during the compile phase. This means that you can use variables and functions before you declare them in your code.

With var, the declaration is hoisted but not the initialization. The variable is set to undefined until the line of code where it is initialized.

With let and const, the declarations are hoisted, but they are not initialized. Accessing them before the declaration results in a ReferenceError.

Function declarations are fully hoisted, meaning both the function name and the function body are hoisted.

Function expressions and arrow functions are not hoisted.

Temporal Dead zone

The Temporal Dead Zone (TDZ) refers to the period between the entering of a scope (like a block or function) and the point at which a variable declared with let or const is initialized. During this period, accessing the variable will result in a ReferenceError.

14. Call by value and call by the difference

15. Execution Context

16. Callstack

17. How asynchronous functions work inside the loop

  1. Using for...of with await.
  2. Using Promise.all for parallel execution: If the order of execution is not important and you want to run async calls in parallel.
  3. Using forEach with async functions: forEach does not work well with async/await because it does not wait for the completion of the async operations. However, you can use it if you handle the promises appropriately.
  4. Using reduce to handle async calls sequentially.
  5. Using traditional for loop with await

Example:

const asyncFunction = async (item) => {
  // Your async code here
};

const processArray = async (array) => {
  await array.reduce(async (promise, item) => {
    await promise;
    await asyncFunction(item);
  }, Promise.resolve());
};

processArray([1, 2, 3, 4, 5]);

Coding challenges

1. What is the output of the snippet below

The JavaScript for-in statement loops through the properties of an Object:

let language = [ 'python', 'javascript'];
let obj = {...language};

console.log('2' in language);
console.log('javascript' in obj);

2. Swap the two variables' values without using the temporary variable

let x = 2, y = 3;
    console.log("Before Swapping: x = " +  x + ", y = " + y);
    // x now becomes 5
    x = x + y;
    // y becomes 2
    y = x - y;
    // x becomes 5
    x = x - y;
    console.log("After Swapping: x = " + x + ", y = " + y);
//using Array
[x,y] = [y,x]

3. Write a function to count the occurrences of each character in the string.

function countCharacterOccurrences(str) { 
  const charCount = {}; 
  for (let char of str) { 
    charCount[char] = (charCount[char] || 0) + 1; 
  } 
  return charCount; 
} 

4. Get the output format of the given input string

let input = 'apple' the output should be "A-Pp-Ppp-Llll-Eeeee"

const sample = (input) => {
    let spl = input.split('');
    
    let res = spl.reduce((acc, val, index) => {
        for(let i=0; i<=index;i++){
            if(i==0)
                acc = acc + val.toUpperCase();
            else acc = acc + val;
        }
        return index==spl.length-1 ? acc : acc+ '-';
    },'');
    
    return res;
}

console.log(sample(str));

5. Remove duplicates from the array or get the unique values in an array

const removeDuplicates = arr => [...new Set(arr)];

console.log(removeDuplicates([1,2,3,22,3,2,4,5,5,5,6,7]));

without using built-in function(set)

const removeDuplicates = arr => {
    let result = [];
    arr.map((val) => {
        if(!result.includes(val)){
            result.push(val);
        };
    });
    
    return result;
}

console.log(removeDuplicates([1,2,3,22,3,2,4,5,5,5,6,7]));

6. String Related tasks

Check if a string is a palindrome

A palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backward

const isPalindrome = str => str === str.split('').reverse().join('');

console.log(isPalindrome('racecar'));

Remove all the vowels from a string

const removeVowels = str => str.replace(/[aeiou]/gi, '');
console.log(removeVowels('hello world'));

Check if a string contains a substring

const contains = (str, subString) => str.includes(subString);
console.log(contains('hello world', 'll'));

Write a JavaScript program to convert a string to a title case (capitalize the first letter of each word).

function titleCase(str) {
    return str.toLowerCase().split(' ').map(function (word) {
        return (word.charAt(0).toUpperCase() + word.slice(1));
    }).join(' ');
}
console.log(titleCase("converting string to titlecase"));

Write a function that determines if two strings are anagrams of each other.

An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

function areAnagrams(str1, str2) { 
  return str1.split("").sort().join("") === str2.split("").sort().join(""); 
} 

Implement a function to reverse a string without using the built-in reverse() method.

function reverseString(str) { 
  let reversed = "; 
  for (let i = str.length - 1; i >= 0; i--) { 
    reversed += str[i]; 
  } 
  return reversed; 
} 

6. Array Related tasks

Sorting an array

const sortAsc = arr => array.sort((a,b) => a -b);
const sortDesc = arr => array.sort((a,b) => b-a);

Get the first n elements of the array

const take = (arr,n) => arr.slice(0,n);
console.log(take([1,2,3,4,5,6],3));

Get the last n elements of the array

const take = (arr,n) => arr.slice(-n);
console.log(take([1,2,3,4,5,6],3));

Check if an array is empty

const isArrayEmpty = arr => Array.isArray(arr) && arr.length > 0;
console.log(isArrayEmpty([1,2,3,4,5,6]));

Find max/min values in an array

Math.max(...array);
Math.min(...array);

Implement a function to find the sum of all the numbers in an array.

Using reduce() method.

function findSum(arr) { 
  return arr.reduce((sum, num) => sum + num, 0); 
} 

7. Find the domain name from an email

const extractDomain = mail => mail.split('@')[1];

console.log(extractDomain('[email protected]'));

8. Flatten a nested array

The flat() method is a built-in JavaScript method that flattens the input array into a new array. This method takes an optional depth parameter, which defines the depth level specifying how deep a nested array structure should be flattened.

// using flat() method

let nestedArray = [1, [2, 3], [4, [5, 6]], 7];
let flatArray = nestedArray.flat(2);
console.log(flatArray); // [1, 2, 3, 4, 5, 6, 7];

// using recursion

function flattenArray(array) {
  return array.reduce((accumulator, value) => 
    Array.isArray(value)? accumulator.concat(flattenArray(value)) : accumulator.concat(value), []);
}
let nestedArray = [1, [2, 3], [4, [5, 6]], 7];
let flatArray = flattenArray(nestedArray);

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

9. Please find the key value in the nested object, if exists return its values else return undefined

const obj ={
  foo: { bar: { biz: 'hello', a: 10 }, w: 1000 },
  sap: {},
  ss: 'welcome'
}

const deepGet = (obj, key = 'foo.bar.biz') => {
    let input = key.split('.');
    let output = input.reduce((acc, val, index) => {
      return acc?.[val] ?? null;
    },obj);
    
    return output;
};

console.log(deepGet(obj));
deepGet(data, ['foo', 'foz', 2]); // 3
deepGet(data, ['foo', 'bar', 'baz', 8, 'foz']); // null

10. Write a javascript program to calculate the factorial of a given number.

A factorial number is the product of all positive integers equal to or less than the given number.

The multiplication of all positive integers say โ€œnโ€, that will be smaller than or equivalent to n is known as the factorial.

function factorial(number) { 
  if (number === 0 || number === 1) { 
    return 1; 
  } else { 
    return number * factorial(number - 1); 
  } 
} 
// OR
function factorial(num) { 
  if (num <= 1) return 1; 
  return num * factorial(num - 1); 
} 

11. Write a JavaScript function to check if a given number is prime.

To check if a given number is prime, loop from 2 to the square root of the number. If any integer evenly divides it, the number is not prime.

function isPrime(num) { 
  if (num <= 1) return false; 
  for (let i = 2; i <= Math.sqrt(num); i++) { 
    if (num % i === 0) return false; 
  } 
  return true; 
} 

12. Find the brackets that match the string in javascript. if the input string is โ€œad(dfd[0]) {d}โ€ then the output should be 1 and count of brackets and if the input string is โ€œad(dfd[0) {dโ€ then the output should be 0.

Hint for the solution

13. Write a program to print all the LEADERS in the array. An element is a leader if it is greater than all the elements to its right side. And the rightmost element is always a leader.

For example:

Input: arr[] = {16, 17, 4, 3, 5, 2}, Output: 17, 5, 2

Real-time coding snippets for the project

1. Generate random string

We can use Math.random() to generate a random string, It is very convenient to generate a unique ID

const randomString = () => Math.random().toString(36).slice(2);

2. Generate a random string of a given length

const randomString = (length =10) => {
    let result = '';
    while(result.length < length) {
        result += Math.random().toString(36).slice(2);
    }
    
    return result.slice(0, length);
}

Random number of a given length

 const generateRandomNumbers = () => {
    let random = [];
    for (let i = 0; i < count; i++) {
      random.push(Math.floor(Math.random() * 10));
    }
    setRandom(random);
  }; 

2. Copy content to the clipboard

const copyToClipboard = text => navigator.clipboard.writeText(text);
copyToClipboard("Hello world");

3. Clear all cookies

const clear = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`));

4. Get the selected text by the user

const getSelectedText = () => window.getSelection().toString();
getSelectedText();

5. Scroll to the top of the page

const goToTop = () => window.scrollTo(0,0);
goToTop();

6. Check whether the user has scrolled to the bottom of a page

const scrolledToBottom = () => document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight;

7. Find out if the current tab is active

const isTabInView = () => !document.hidden

8. Redirect the user to a URL

const redirect = url => location.href = url;

redirect("https://www.google.com/");

9. Open the browser print box

const showPrintDialog = () => window.print();

10. Generate a random boolean value

const randomBoolean = () => Math.random() >=0.5;

randomBoolean();

11. Generate a random number between two values

// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;

12. Check the given number is an integer

const isInteger = (num) => num % 1 === 0;

13. Check if a variable is an array

const isArray = arr => Array.isArray(arr);

14. Date related tasks

Check if the date is the weekend getDay() will return the day of the week (from 0 to 6) of a date.

const isWeekend = date => {
    console.log(date.getDay());
    return [0, 6].indexOf(date.getDay()) !== -1;
}
console.log(isWeekend(new Date('2024-06-22'))); // sat
console.log(isWeekend(new Date('2024-06-23'))); // sun

getting day of the week

function getDayName(dayIndex) {
  const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  return days[dayIndex];
}
// Create a Date object for February 26, 2024 (a Tuesday)
const today = new Date('2024-02-26');
// Using getDay() to retrieve the day of the week
const dayOfWeek = today.getDay();

console.log(`Today is a ${getDayName(dayOfWeek)}.`);

Calculate the number of days between two dates

const dayDiff = (date, date2) => Math.ceil(Math.abs(date - date2) / 84600000);

console.log(dayDiff(new Date('2023-05-19'), new Date('2024-05-19')));

15. Capitalize a string

const capitialise = str => str.charAt(0).toUpperCase() + str.slice(1);

console.log(capitialise('hello world'));

11. Write a JavaScript function that takes an array of numbers and returns a new array with only the even numbers.

function filterEvenNumbers(numbers) { 
  return numbers.filter(num => num % 2 === 0); 
}

11. Generate a random number between two values

// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
```## 11. Generate a random number between two values
```javascript
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;

javascript-programming-language's People

Contributors

kaleeswarip avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.