Javascript programming language guidelines/notebook
Javascript core basic
- Variables and Data Types
- Operators
- Control Structures
- Functions
- Objects and Arrays
- Asynchronous javascripts
- Strings
- Dates Handling
- Map and Set
- DOM Manipulation APIs
- Web storage in JS
Working of JS Engine
Advanced Topics
- Event Loop, Microtasks, Macrotasks
- Events: DOMContentLoaded, load, beforeunload, unload
- Polyfilles
- Memoization
- Generator Functions
- Iterator
- Web APIs
- Design Patterns
Coding Concepts
- Currying
- Closures
- IIFE
- Destructuring
- Inheritance
- Spread and Rest operator
- Callback, Promises, and Async Await
- Debouncing and Throttling
- Event Propagation, Event Bubbling, Capturing, and Delegation
- setTimeout, setInterval
- High Order Functions
- Call, Apply, Bind
- Hoisting and Temporal dead zone
- Call by value and call by the difference
- Execution Context
- Callstack
- How asynchronous functions work inside loops
- Grouping the objects by its keys/values
Javascript coding practices/Challenges
- What is the output of the snippet below
- Swap the two variables' values without using the temporary variable
Javascript Real-time code snippets
- Variables and Data Types
- Operators
- Control Structures
- Functions
- Objects and Arrays
- Asynchronous javascripts
- Strings
- Dates Handling
- Map and Set
- DOM Manipulation APIs
- Web storage in JS
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
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
- Arithmetic Operators:
+
,-
,*
,/
,%
,++
,--
. - Comparison Operators:
==
,===
,!=
,!==
,>
,<
,>=
,<=
. - Logical Operators:
&&
,||
,!
.
if
, else if
, else
, switch
.
if (age > 18) {
console.log('Adult');
} else {
console.log('Minor');
}
for
, while
, do...while
, for...of
(ES6)
, for...in
.
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]}`);
}
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]}`);
}
loop breaking statements are used to alter the flow of loop iterations.
Two primary statements are used for this purpose: break
and continue
.
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);
}
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);
}
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}`);
}
}
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;
}
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, 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
- Objects: Collections of key-value pairs.
`const user = {
name: 'Alice',
age: 25,
greet: function() {
return 'Hello, ' + this.name;
}
};
console.log(user.greet()); // Hello, Alice
- Arrays: Ordered collections of values.
`const numbers = [1, 2, 3, 4, 5];
console.log(numbers[0]); // 1
Functions passed as arguments to other functions.
function fetchData(callback) {
setTimeout(() => {
callback('Data loaded');
}, 1000);
}
fetchData(data => console.log(data));
Represent future values or errors.
`const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Data loaded'), 1000);
});
promise.then(data => console.log(data));
Syntactic sugar for working with Promises.
async function fetchData() {
const data = await promise;
console.log(data);
}
fetchData();
Refer Callbacks-promises-asyncs-await
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
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
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
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.
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.
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.
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 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.
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]
}
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
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
}
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
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 (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.
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');
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';
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');
style
Directly modifies the CSS styles of an element.
element.style.color = 'blue';
element.style.fontSize = '20px';
`classList Provides methods to add, remove, and toggle classes.
element.classList.add('newClass');
element.classList.remove('oldClass');
element.classList.toggle('active');
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);
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);
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;
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).
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.
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.
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.
- Event Loop, Microtasks, Macrotasks
- Events: DOMContentLoaded, load, beforeunload, unload
- Polyfilles
- Memoization
- Generator Functions
- Web APIs
- Design Patterns
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.
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, or simply tasks, are scheduled in the task queue. This includes:
- setTimeout
- setInterval
- setImmediate
- I/O tasks
- UI rendering (in browsers)
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.
- 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.
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');
});
(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.
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;
};
}
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
})();
}
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 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.
- 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 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)
}
})();
- 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
}
- Asynchronous Programming
- Lazy Evaluation: Generators can be used to implement lazy evaluation, where values are computed only when needed.
- 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.
- 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();
- Pausing and Resuming Execution
- Combinatorial Generation
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.
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 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
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Provider
- HOC
Structural Patterns
Behavioral Patterns
- Observer
- Command
- Strategy 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
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
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
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
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!
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.
- Currying
- Closures
- IIFE
- Destructuring
- Inheritance
- Spread and Rest operator
- Callback, Promises, and Async Await
- Debouncing and Throttling
- Event Propagation, Event Bubbling, Capturing, and Delegation
- setTimeout, setInterval
- High Order Functions
- Call, Apply, Bind
- Hoisting and Temporal dead zone
- Call by value and call by the difference
- Execution Context
- Callstack
- How asynchronous functions work inside loops
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.
- 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.
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));
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"));
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))
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).
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.
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
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;}
};
}
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 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 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.
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
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.
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]
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' }
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.
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 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
.
The spread and rest operators in JavaScript are both represented by three consecutive dots (
...)
.
The spread operator is used to expand elements of an iterable (like an array or a string) into individual elements.
- 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]
- 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 }
- 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
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.
- 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);
- 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]
- 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 }
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();
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 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.
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 describes the order in which events are handled in the DOM.
- 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.
- Target Phase:
The event reaches the target element, where it originated. Event listeners on the target element can handle the event.
- 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 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 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 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.
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.
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 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
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
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.
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"
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
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
-
Arguments Passing:
call
andapply
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.
-
Immediate vs Delayed Execution:
call
andapply
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.
-
Original Function vs New Function:
call
andapply
invoke the original function with the specified context and arguments.- bind creates a new function that, when invoked, uses the specified context.
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.
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
.
- Using
for...of
withawait
. - Using
Promise.all
for parallel execution: If the order of execution is not important and you want to run async calls in parallel. - Using
forEach
with async functions:forEach
does not work well withasync/await
because it does not wait for the completion of the async operations. However, you can use it if you handle the promises appropriately. - Using reduce to handle async calls sequentially.
- 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]);
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);
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]
function countCharacterOccurrences(str) {
const charCount = {};
for (let char of str) {
charCount[char] = (charCount[char] || 0) + 1;
}
return charCount;
}
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));
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]));
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'));
const removeVowels = str => str.replace(/[aeiou]/gi, '');
console.log(removeVowels('hello world'));
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"));
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("");
}
function reverseString(str) {
let reversed = ";
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
const sortAsc = arr => array.sort((a,b) => a -b);
const sortDesc = arr => array.sort((a,b) => b-a);
const take = (arr,n) => arr.slice(0,n);
console.log(take([1,2,3,4,5,6],3));
const take = (arr,n) => arr.slice(-n);
console.log(take([1,2,3,4,5,6],3));
const isArrayEmpty = arr => Array.isArray(arr) && arr.length > 0;
console.log(isArrayEmpty([1,2,3,4,5,6]));
Math.max(...array);
Math.min(...array);
Using reduce() method.
function findSum(arr) {
return arr.reduce((sum, num) => sum + num, 0);
}
const extractDomain = mail => mail.split('@')[1];
console.log(extractDomain('[email protected]'));
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
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);
}
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.
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
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);
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);
};
const copyToClipboard = text => navigator.clipboard.writeText(text);
copyToClipboard("Hello world");
const clear = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`));
const getSelectedText = () => window.getSelection().toString();
getSelectedText();
const goToTop = () => window.scrollTo(0,0);
goToTop();
const scrolledToBottom = () => document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight;
const isTabInView = () => !document.hidden
const redirect = url => location.href = url;
redirect("https://www.google.com/");
const showPrintDialog = () => window.print();
const randomBoolean = () => Math.random() >=0.5;
randomBoolean();
// Returns a random number between the min and max variable's values
const randomNumber = Math.random() * (max - min) + min;
const isInteger = (num) => num % 1 === 0;
const isArray = arr => Array.isArray(arr);
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')));
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);
}
// 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;